thecodingmachine/discovery
Discover PHP classes implementing a given interface by scanning your Composer classmap at runtime. Thecodingmachine/Discovery makes building plugin systems and auto-registering services simple, with no manual configuration or boilerplate required.
Install it at the root of the project (it works across all Composer packages, so it belongs in the app, not a library):
composer require thecodingmachine/discovery
Composer 2.2+ will prompt you to allow the plugin — accept it (config.allow-plugins), otherwise nothing is generated.
The mental model has two halves. Publishers (any installed package) declare assets in their own composer.json:
"extra": {
"discovery": {
"my-app-assets": [
{ "type": "file", "value": "dist/widget.js" },
{ "type": "file", "value": "dist/widget.css" }
]
}
}
Consumers read those assets back through a single API:
use TheCodingMachine\Discovery\Discovery;
foreach (Discovery::getInstance()->get('my-app-assets') as $asset) {
echo $asset->getValue(); // "dist/widget.js"
}
First real use case in a Laravel app: let a Composer-installed package ship its own JS/CSS, then copy them into public/vendor/<package>/ in a post-autoload-dump hook or a small Artisan command — no manual copying, no hard-coded list of vendor packages in your app.
Type-as-tag registry. The string key in extra.discovery is arbitrary. Use it as a namespace: laravel.route-file, laravel.migration-path, admin-menu-item, vite.entry. This is the package's real value — a decentralized plugin registry read from Composer metadata rather than a central config array.
Publishing to public/. The "publish" workflow copies declared files into a web-accessible directory, tracking a manifest so unchanged files aren't recopied:
Discovery::publishAssets(public_path('vendor'), force: false);
Wire this into your deploy script (after composer install --no-dev) or a php artisan discovery:publish command. In dev, pass force: true or delete the manifest when package versions change.
Priority-based overrides. Each asset entry can carry a priority. Higher-priority entries should win when you want the app to override a vendor-provided asset — useful for the laravel.* type where your app config should shadow a package default. Sort/iterate and take the first match, or merge all with the app first.
Non-file assets. Assets can be strings (a class name, a route file, a config fragment). A publisher can register a service provider class as a string asset, and your app discovers all of them and calls register(). This is a poor-man's package discovery mechanism — the same idea Laravel uses natively now, but driven by Composer.
Laravel integration tip. Wrap reads behind a cached service provider. The discovery file lives under vendor/ and is regenerated on composer dump-autoload; resolve it once in boot() and bind results into the container so you're not re-parsing on every request.
scripts from the root composer.json. Any package documenting a post-autoload-dump hook will not fire for its consumers. Keep the hook in your app's composer.json (or rely on the plugin), and re-run composer dump-autoload after changing any extra.discovery block.get() returns an empty array, the generated file is almost certainly out of date. Regenerate it, and inspect it directly — it's the ground truth of what was published.FileAsset, prepend the package's install path (available via Composer's installed.json, or the asset's own location metadata). Don't assume base_path().allow-plugins silently breaks generation on Composer 2.2+. Check composer config allow-plugins first when nothing works.composer.json extra block generally won't throw at read time — it just drops entries. Validate entries (type + value keys) in your own code if you depend on them.composer.json files. Fine at deploy, wasteful per-request — cache the resolved list.extra.laravel service-provider discovery or a simple composer.json merge in your own service provider covers the same ground; use this package when you specifically need vendored package assets copied to public/.How can I help you explore Laravel packages today?