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

Discovery Laravel Package

puli/discovery

Puli Discovery is a PHP library for locating and managing resources across packages. It provides a discovery API to register resource bindings, find files by type or path, and integrate resource lookup into Puli-based applications.

Deep Wiki
Context7

Getting Started

puli/discovery is the read/write API for Puli’s artifact registry: you publish bindings (a resource + a binding type + parameters) and later ask the DiscoveryManager for them. It sits underneath the puli CLI, so the first thing to check is whether you actually need the CLI or just the library.

Minimal install:

composer require puli/discovery:^1.0

Note the package is abandoned (last release Jan 2016). Pull it into a sandbox or a legacy project only, and pin the version explicitly.

Where to look first, in this order:

  1. Puli\Discovery\Api\Discovery — the interface you should type-hint against. Methods: getTypes(), getType(), hasType(), getBindings(), findBindings(), findBindingsByType(), hasBindings(), hasBinding().
  2. Puli\Discovery\DiscoveryManager — the concrete implementation. Constructor takes a DiscoveryStore, an optional TypeRegistry, and an optional EventDispatcherInterface.
  3. Puli\Discovery\Api\Type\BindingType + Puli\Discovery\Api\Binding\ResourceBinding — what you’re actually storing.
  4. Puli\Discovery\Store\JsonDiscovery (or NullDiscovery / KeyValueStoreDiscovery) — where bindings are persisted.

First use case: a lightweight plugin registry. Tag classes with a custom type, then resolve them in a bootstrap step:

$discovery = new DiscoveryManager(new JsonDiscovery(__DIR__ . '/discovery.json'));
$discovery->defineType(new BindingType('app/plugin'));

$discovery->addBinding(new ResourceBinding($resource, 'app/plugin'));
foreach ($discovery->findBindingsByType('app/plugin') as $binding) {
    $class = $binding->getResource()->getPath();
    // register $class somewhere
}

If you’re in Laravel, the practical equivalent is a service provider that constructs the DiscoveryManager once and binds Discovery into the container.

Implementation Patterns

Service provider + singleton. Puli has no Laravel integration, so wire it yourself. Register the manager as a singleton and resolve it by interface:

$this->app->singleton(Discovery::class, function ($app) {
    $store = new JsonDiscovery($app->storagePath('framework/puli.json'));
    $manager = new DiscoveryManager($store);

    foreach (config('puli.types', []) as $typeName) {
        $manager->defineType(new BindingType($typeName));
    }

    foreach ($app['events']->makeListeners ?? [] as $l) { /* attach */ }

    return $manager;
});

Keep this in a dedicated PuliServiceProvider so the package is removable in one file.

Programmatic publication over config. Puli’s native workflow is puli.json in each package plus the CLI to publish. In Laravel you rarely control installed package manifests, so prefer publishing bindings programmatically during boot (or in a cached bootstrap step) and read via findBindingsByType(). Treat the JSON store as a build artifact, not source.

Custom binding types. Subclass BindingType (or implement the interface) when a plain type name isn’t enough — e.g. a type that carries a serializer/validator for parameters. Register once with defineType(); look it up with getType($name). This is the main extension point beyond listeners.

Event-driven side effects. DiscoveryManager dispatches BindingTypeRegisteredEvent, BindingTypeRemovedEvent, ResourceBindingAddedEvent, and ResourceBindingRemovedEvent. Implement Puli\Discovery\Api\DiscoveryListener and addListener() to keep an in-memory index in sync, or to invalidate a cache whenever bindings change. This mirrors Laravel’s event system closely enough that you can forward Puli events onto the Laravel dispatcher in the provider.

Two-phase boot. In a Laravel app, discovery is naturally two-phase: (1) publish bindings during register()/a cached bootstrap, (2) consume them in boot() or in console commands. Avoid querying bindings inside hot request paths; resolve once and cache the result, because JsonDiscovery decodes its file per call in the reference implementation.

Query filtering. findBindings($typeName, $query) accepts a query string understood by the underlying store; findBindingsByType() returns all bindings of a type. For most registries the latter is all you need, and it’s the more predictable of the two.

Testing. Swap JsonDiscovery for NullDiscovery in tests, or point JsonDiscovery at a temp file per test and rebuild the manager. Because everything is behind the Discovery interface, faking it is trivial.

Gotchas and Tips

  • This package is abandoned and ancient. Last release 2016, PHP 5.x/early-7.x-era signatures. Expect friction on PHP 8.x (no return types, possibly each()-era idioms in transitive deps). Isolate it behind your own interface so removal is a one-file change.
  • Puli itself is discontinued. The problem Puli solved — discovering files and classes across Composer packages — is largely handled today by Composer’s extra section plus PackageManifest / installed.json. For new Laravel work, prefer a Composer-extra-based plugin registry; use puli/discovery only if you’re maintaining a Puli-era codebase.
  • Store is the source of truth, not the manager. In-memory changes are not automatically flushed by every store implementation — check whether your store requires an explicit save/flush. Losing bindings after a request is the most common first bug.
  • Type collisions throw. Calling defineType() twice with the same name raises an exception. Puli’s bootstrapping typically re-registers built-in types (puli/binding-type/class, etc.) — guard with hasType() before defining.
  • Resource vs. path confusion. ResourceBinding wraps a puli/repository Resource, not a raw string. getResource()->getPath() is usually what you want, but it may be a repository-relative path; getRepositoryPath() or the puli:// scheme is what you need if you’re feeding it to an autoloader.
  • Config file quirks. The CLI expects puli.json alongside composer.json. If your package manager also writes there (or if you deploy without the file), a stale discovery.json silently yields zero bindings. Assert hasBindings() in a health check.
  • Debugging. Dump $discovery->getTypes() and $discovery->getBindings() before blaming find logic — most “discovery doesn’t work” reports are empty registries, not broken queries. Attach a listener that logs every ResourceBindingAddedEvent during development.
  • Caching. Treat any discovery result as expensive and cache it under a key derived from the store’s mtime/hash. Invalidate from ResourceBindingAddedEvent/ResourceBindingRemovedEvent listeners rather than by hand.
  • Extension points worth knowing. DiscoveryListener (react to changes), custom BindingType (validate/serialize params), and the DiscoveryStore implementations (swap JSON for a DB-backed store without touching call sites). Everything else in the package is implementation detail.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console