kmi/system-information-bundle
kmi/system-information-bundle is a Symfony bundle that collects and exposes system information, making environment and runtime details available for diagnostics, monitoring, and admin dashboards.
This is not a Laravel package. kmi/system-information-bundle is a Symfony bundle built for Sonata Admin. It has no service provider, no facade, no config/system-information.php, and no Laravel auto-discovery entry. Add to that 0 stars, 0 dependents, archived status, and a 0.065 score, and the only defensible first step is to treat it as specification material, not a dependency.
Minimal steps to get value from it:
composer require it. Its symfony/* and sonata-project/* constraints will fight the Symfony components Laravel already pins, and Composer will almost certainly refuse resolution under PHP 8.1+.DependencyInjection/ extension and the Sonata Admin subclass. That admin class is where the real data collection lives — the show/form mapper fields are the author's list of "systemically relevant" data points.Resources/views/ to see which fields were rendered together and in what order. That is the layout you are porting.Your first use case should be a single Artisan command:
// app/Console/Commands/SystemInfo.php
public function handle(): int
{
$this->table(['Key', 'Value'], [
['Environment', app()->environment()],
['Debug', config('app.debug') ? 'on' : 'off'],
['PHP', PHP_VERSION],
['Laravel', app()->version()],
['Cache', config('cache.default')],
['Queue', config('queue.default')],
['Disk free', $this->bytes(disk_free_space(base_path()))],
]);
return self::SUCCESS;
}
Laravel already ships the direct analogue — php artisan about — which covers environment, drivers, PHP version, and loaded packages. Start there, then extend with the extra rows this bundle surfaces (disk headroom, extension list, DB reachability, debug flag) and gate the command behind an environment check.
If you genuinely need it running (you maintain a legacy Symfony/Sonata service alongside Laravel work):
config/bundles.php, then wire sonata_admin and sonata_block. Grant an explicit role — ROLE_SUPER_ADMIN or a dedicated ROLE_SYSTEM_INFO — and never leave the route on IS_AUTHENTICATED_ANONYMOUSLY.templates/bundles/...) so the archived vendor tree stays untouched and replaceable.The Laravel port — the thing you should actually build. Map fields one-to-one:
| Symfony bundle surfaces | Laravel source |
|---|---|
| PHP / framework versions | PHP_VERSION, app()->version() |
| Environment + debug flag | app()->environment(), config('app.debug') |
| Loaded packages | composer show --direct, composer audit |
| Disk headroom | disk_free_space(), cached 5 min |
| DB reachability + size | DB::select('select 1'), DB::select('select sum(data_length+index_length) from information_schema.tables') |
| Cache/queue drivers | config('cache.default'), config('queue.default') |
| Loaded extensions | get_loaded_extensions() |
| App version / commit | config('app.version'), trim(shell_exec('git rev-parse --short HEAD')) |
Then expose it three ways, in increasing order of exposure:
system:info) — for ops and CI smoke tests./up health endpoint — a curated subset (200/500 only). Never the full panel.Wrap the collection in a single service so all three consumers share it:
final class SystemInfo
{
public function __construct(private Cache $cache) {}
public function collect(): array
{
return $this->cache->remember('system-info', now()->addMinutes(5), fn () => [
'php' => PHP_VERSION,
'laravel' => app()->version(),
'environment' => app()->environment(),
'debug' => config('app.debug'),
'extensions' => get_loaded_extensions(),
'disk_free' => @disk_free_space(base_path()),
'db_ok' => $this->dbReachable(),
]);
}
private function dbReachable(): bool
{
try { DB::select('select 1'); return true; }
catch (\Throwable) { return false; }
}
}
Installation will fail, and that is the expected finding. Attempt a composer require in a scratch Laravel 11 app once, record the resolution error, and move on. Do not "fix" it by pinning old Symfony components — the class-collision surface is unbounded.
Information disclosure is the real risk, not performance. The panel surfaces server paths, PHP config, extension lists, and possibly environment variables. Anything rendered from it belongs behind admin authorization and must never be reachable from a public route. Curate the field list — do not dump phpinfo() equivalents into a view.
Privilege errors are silent or fatal depending on host. disk_free_space(), /proc reads, and shell_exec('git ...') all behave differently under containerized, chrooted, or restricted-disable_functions PHP. Wrap every probe in try/catch (or @ where the failure is non-actionable) and render null as — rather than throwing inside a dashboard render.
Cache the payload. The panel is not a hot path, but a busy Sonata-style dashboard render will stat the filesystem on every load. A 5–15 minute cache TTL is correct; also clear it after deploys if you show the commit SHA.
Silent staleness is the failure mode of abandoned panels. An unmaintained renderer keeps returning plausible-looking data after a PHP upgrade — no error, just wrong rows. Pin your own collector to a test that asserts each key is non-null so drift surfaces in CI.
Do not confuse this with observability. It displays static facts ("what is installed"). It does not answer "what is happening" — that is spatie/laravel-health, Laravel Pulse, Sentry, or Prometheus. Keep the roadmap items separate.
If you fork the Symfony bundle anyway, own it explicitly: a named maintainer, a fork in your org, and a review cadence tied to your PHP upgrade cycle. Zero stars and zero dependents mean no upstream triage and no community corpus to search when it breaks.
Extension point worth copying: the bundle's provider-interface pattern — a taggable collection of information providers — is a good shape for your Laravel version too. A SystemInfoProvider interface with tagged implementations makes it trivial to add "last queue heartbeat" or "last deploy" later without touching the collector.
How can I help you explore Laravel packages today?