Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Package Versions Laravel Package

ocramius/package-versions

PackageVersions gives PHP applications fast, zero-I/O access to the installed versions of Composer dependencies. Version data is compiled from composer.lock at install time, so \PackageVersions\Versions::getVersion() returns a package's version and commit hash without any runtime disk reads—ideal fo...

View on GitHub
Deep Wiki
Context7

Getting Started

composer require ocramius/package-versions

Then turn on the optimized autoloader (the package's own README recommendation) so the class lookup stays cheap:

{
    "config": {
        "optimize-autoloader": true
    }
}

First call. Confirm it resolves in your app before designing anything around it:

php artisan tinker
>>> PackageVersions\Versions::getVersion('laravel/framework')
=> "11.9.2@0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

Two things to notice immediately, because they shape every pattern below:

  • The return value is version@reference, not a clean semver string. You either split it or use getShortVersion().
  • Versions::getVersion() is a static call on a final class. It works but it is not injectable — wrap it before it leaks into controllers.

Where to look first. The README's "Purpose" + "Use-cases" sections (it is a two-minute read), then PackageVersions\Versions itself: getVersion(), getShortVersion(), rootPackageName(). Nothing else exists — there is no config file, no service provider, no events in 2.x.

First real use case for a Laravel app: a /api/_version style endpoint or asset cache-buster that reports what is actually installed. The competing approaches — exec('composer show ...'), parsing composer.lock by hand, or a git describe shell-out — either require a Composer binary in production or add per-request I/O. This package is the boring, cheap version of that: resolve once, string out.

Check your Composer version. If the project is on Composer 2, Composer\InstalledVersions ships with Composer itself and covers most of the same ground (getPrettyVersion(), getReference(), isInstalled(), satisfies()). Decide deliberately whether you want the extra dependency; for a Laravel app already carrying 90 packages it is usually a style choice, not a weight problem — but know which one you are standardising on before two different call sites appear.

Implementation Patterns

1. Wrap it in a value object, bind it once

Versions is static and untestable by default. Give your app a seam:

namespace App\Support;

use PackageVersions\Versions;

final class PackageVersion
{
    /** @var array<string, string> */
    private array $cache = [];

    public function version(string $package): ?string
    {
        if (isset($this->cache[$package])) {
            return $this->cache[$package];
        }

        try {
            return $this->cache[$package] = explode('@', Versions::getVersion($package))[0];
        } catch (\Throwable) {
            return null; // unknown package, or metadata not readable
        }
    }

    public function app(): string
    {
        return (string) config('app.build_version', 'dev');
    }

    public function framework(): string
    {
        return $this->version('laravel/framework') ?? 'unknown';
    }
}

Bind it as a singleton in AppServiceProvider::register() ($this->app->singleton(PackageVersion::class)). The singleton matters: the first resolution touches disk, and you do not want that per call site. Tests then bind a fake and never touch Composer metadata at all.

2. Asset / cache-busting identifier

The classic use: a value that changes when the deployed code changes.

public function assetId(): string
{
    return substr(sha1($this->framework().'|'.$this->app()), 0, 12);
}

Use it as a cache key suffix, an ETag, or an email template signature. Prefer generating it at deploy time (a composer script or CI step writing config/build.php) if you want it frozen at build rather than at runtime; prefer runtime resolution if you want dev and prod to diverge naturally.

3. Version / health endpoint

Route::get('/_version', function (PackageVersion $v) {
    return response()->json([
        'app'       => $v->app(),
        'framework' => $v->framework(),
        'php'       => PHP_VERSION,
    ]);
})->middleware('throttle:60,1');

Gate it: ->middleware(['auth', 'can:viewOps']) or hide it behind app()->isProduction() checks. In practice, do not put this on the public internet — version disclosure is a real (if low) signal for attackers.

4. Tagging errors and outbound calls

Two places where "what version is running" pays for itself immediately:

  • Exception reporting. Tag Sentry/Firebug/Nightwatch events with laravel/framework and your own build id in a before_send hook or in App\Exceptions\Handler. When a bug only reproduces on one deploy, you will be glad you did.
  • Outbound HTTP headers. Third-party APIs often want a User-Agent that identifies your integration; GitHub rejects requests without one.
Http::withHeaders([
    'User-Agent' => sprintf('MyApp/%s (Laravel %s)', $v->app(), $v->framework()),
])->get('https://api.github.com/user/repos');

5. Dependency-aware feature gates

Useful for packages you cannot control (internal SDKs, payment gateways):

if ($v->version('acme/payments-sdk') && version_compare($v->version('acme/payments-sdk'), '3.2.0', '>=')) {
    // new payout endpoint only exists in 3.2+
}

Prefer composer/semver's Semver::satisfies($version, '^3.2') over version_compare when constraints get interesting, and prefer Composer\InstalledVersions::satisfies() if you have already standardised on Composer 2.

6. Artisan surface

Add a line to php artisan about or a dedicated php artisan app:version command. Ops people run one command instead of docker exec ... composer show. This is the highest ratio-of-value-to-effort use case in the whole package.

7. Do not put it in a hot path or a loop

Resolve once in a provider, store the result in a config array or a singleton, inject that. composer dump-autoload -o in your deploy pipeline keeps the autoload side free; the metadata read itself is a single cached pass.

Gotchas and Tips

The return format is version@reference. Everything downstream wants just the version. explode('@', $v)[0] or getShortVersion(). Remember dev dependencies can return dev-main-style values, so do not assume semver parsability — validate before feeding into version_compare.

It needs Composer metadata on disk at runtime. Since 2.0 this is a plain library, not a Composer plugin: it resolves from composer.lock (and composer.json for rootPackageName()) rather than a generated class file. Practical consequences:

  • composer config lock false projects are unsupported — there is no lock file to read.
  • Docker images that do COPY --from=builder /app/vendor /app/vendor without composer.lock and composer.json will fail at the first call. Copy the whole app root, or accept the failure — do not discover it from a 500 in production.
  • Non-standard vendor-dir setups deserve a smoke test.

composer install --no-dev is not the same as "not installed". The lock file still lists dev packages, so a version can be resolvable for a package that is physically absent in production. Never use "a version came back" as proof the class exists — guard with class_exists() or InstalledVersions::isInstalled() if you are feature-detecting.

Your own app has no version in the lock file. rootPackageName() gives you the name. For the app's version, inject a build id / git SHA / composer.json version field through config at deploy. Do not expect getVersion(rootPackageName()) to be meaningful.

Unknown packages: getVersion() throws for a package that is not in the lock. In a controller, that is a 500. Always try/catch at the seam (as in the wrapper above) and decide what null means for each call site.

Static in-process cache. The value is resolved once per process and reused — great for Octane and queue workers, but it also means the value never changes mid-process. That is correct behaviour for deployed code, but it means you cannot "test" a version change without restarting the worker. Deploy scripts must restart Octane/queue workers anyway; this just makes it mandatory.

Config caching bakes the value at config:cache time. If you ever call the package inside config/*.php, remember those files are evaluated once and serialised. If your build pipeline runs config:cache in an image before prod dependencies are installed, the cached value is a lie. Prefer resolving in a service provider or a computed binding.

Optimize the autoloader; do not skip scripts in ways that break your build. Older 1.x releases had documented friction with --classmap-authoritative and --no-scripts; 2.x is much simpler, but keep composer dump-autoload -o in the pipeline and keep composer.lock committed and fresh (composer update --lock if it drifts). A stale lock gives you confidently wrong answers.

Do not shell out instead. exec('composer show --format=json') in a request is exactly the anti-pattern this package exists to kill: process spawn, no Composer binary in the runtime image, filesystem I/O per call, and output that differs across Composer versions.

Extension points: there are none, and that is the point. No config, no events, no plugin hooks in 2.x. Your extension point is your own wrapper. Keep it the only place that imports PackageVersions\Versions — then swapping to Composer\InstalledVersions, or to a build-time generated config/versions.php, is a one-file change rather than a grep across the app.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log