asgoodasnu/version-bundle
asgoodasnu/version-bundle is a PHP package that adds flexible versioning support to your application, making it easy to define, manage, and expose version information through a simple, configurable integration.
First, the packaging reality check: asgoodasnu/version-bundle is a Symfony bundle, not a Laravel package. The -bundle suffix, DependencyInjection/, and Resources/config/ layout are Symfony conventions. There is no extra.laravel.providers, no facade, no service provider — Composer autodiscovery will register nothing in a Laravel app. In practice you have two paths:
composer require asgoodasnu/version-bundle, register in config/bundles.php, configure under config/packages/asgoodasnu_version.yaml.Where to look first in the repo:
DependencyInjection/Configuration.php — the authoritative list of config keys (version string, build number, commit hash, etc.). Everything you replicate in config/version.php is derived from this file.Resources/config/services.* — tells you whether the version is a scalar parameter, an %env()% reference, or resolved at runtime from a file/composer.json. This dictates whether you can set it at build time.Minimal Symfony setup:
# config/packages/asgoodasnu_version.yaml
asgoodasnu_version:
version: '%env(APP_VERSION)%'
# check Configuration.php for build/commit keys and their exact names
Equivalent Laravel port (two files):
// config/version.php
return [
'version' => env('APP_VERSION', 'dev'),
'commit' => env('APP_COMMIT'),
'built' => env('APP_BUILT_AT'),
];
// app/Http/Controllers/VersionController.php
public function __invoke()
{
return response()->json([
'version' => config('version.version'),
'commit' => config('version.commit'),
'built' => config('version.built'),
])->header('Cache-Control', 'public, max-age=60');
}
Route::get('/version', VersionController::class);
That endpoint is the day-one use case and covers most of what this bundle class is used for.
Ops probe endpoint. A flat, unauthenticated, cacheable /version JSON response that your load balancer, Kubernetes sidecar, or release dashboard hits after deploy. Keep it out of auth middleware and out of the session; make it cheap.
Twig / Blade footer stamp. Rendering v1.4.2 (a1b2c3d) in the footer or admin chrome is the other primary use. In Laravel, register a Blade directive or share the value via View::share('appVersion', config('version.version')) in a service provider — do not call env() from templates.
Injected into services for observability. Inject the version value into your HTTP client wrapper so outbound calls carry User-Agent: MyApp/1.4.2, and into your error reporter so exceptions are tagged with the release. In Laravel, bind a small AppVersion value object in the container:
$this->app->singleton(AppVersion::class, fn () => new AppVersion(
config('version.version'),
config('version.commit'),
));
Build-pipeline injection. The version must be baked in at image build time, not read at runtime. In Docker: ARG APP_VERSION → ENV APP_VERSION → php artisan config:cache. A .git directory or git describe call will not exist in production containers.
CI-driven source of truth. Derive the version from the tag (git describe --tags --always) or a VERSION file, pass it to the build, and never hand-edit a version constant. The bundle centralises reading the value; your pipeline should centralise producing it.
Per-environment fallback chain. Production reads the env var; local dev falls back to dev plus short commit hash. Document the fallback so nobody debugs a null version in staging.
Comparison / gating (use sparingly). version_compare(config('version.version'), '2.0.0', '>=') is fine for feature flags during a rollout. It is not a substitute for a real feature-flag system — see gotchas.
Laravel env() after config:cache. This is the single most common failure. Any env('APP_VERSION') call made outside a config/*.php file returns null in production. Always read through config('version.version').
Config and container caching in Symfony. The bundle's configuration is compiled into the container. Changing config/packages/asgoodasnu_version.yaml without bin/console cache:clear (or cache:warmup) leaves you with stale values and a confusing debugging session.
The deploy-vs-response cache gap. A /version endpoint with max-age=3600 will happily report the previous release to your monitoring for an hour after a deploy. Either keep the TTL short or add a ?cb= cache-buster to the probe URL.
git describe in production is a trap. Most CI images exclude .git and run --no-dev, so anything reading composer.json at runtime may work while .git-based resolution silently returns a fallback. Verify the resolved value inside the actual container, not on your laptop.
Don't leak internals needlessly. Commit hashes on public endpoints are usually fine, but if the repository is private, treat the hash as low-grade recon. Consider gating /version to internal networks or splitting "public version" from "internal build metadata."
Never use the version string for security or authorisation decisions. It is self-reported metadata, trivially spoofed at the HTTP layer, and meaningless to a client that can be replayed.
Semver comparisons are fragile. version_compare breaks on dev, 1.4.2-rc.1+build.7, or a missing tag. Normalise before comparing, or use an explicit feature flag instead.
Symfony bundle config keys are not guessable. The YAML root key and inner key names must match DependencyInjection/Configuration.php exactly. If you get a "Unrecognized option" error, that file — not the README — is the source of truth.
Extension point for custom resolution. If the version needs to come from a database row or an external API, the idiomatic Symfony move is to replace the version service definition in your app's services.yaml rather than fork the bundle. In Laravel, swap the AppVersion binding in a service provider — that is your seam.
Zero community surface means you own it. With no stars and no dependents, there will be no upstream fix if a future Symfony/PHP release breaks the bundle. Because the surface is tiny, the honest long-term move for a Laravel team is the port above; for a Symfony team, pin the version and keep the replacement service registered so you can remove the dependency later without a refactor.
How can I help you explore Laravel packages today?