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

Manager Laravel Package

puli/manager

Puli Manager provides the command-line tools for Puli, a PHP resource management system. It lets you initialize and configure repositories, map package resources, publish assets, and inspect resource paths across Composer packages.

Deep Wiki
Context7

Getting Started

Puli Manager is the low-level library behind the puli CLI. It owns reading, validating, mutating and persisting the project’s puli.json file and the derived .puli/ directory. Day to day you will almost always reach for it through puli/cli; the programmatic API is for build scripts, Composer hooks and custom tooling.

Minimum viable setup:

composer require --dev puli/cli      # pulls in puli/manager
vendor/bin/puli init                 # creates puli.json + .puli/

If you only want the library:

composer require puli/manager

Where to look first, in order:

  1. Puli\Manager\Api\Puli — the facade. Everything hangs off it: getConfigManager(), getPackageManager(), getBindingManager(), getDiscoveryManager(), getServerManager(), getModuleManager(), getModuleList(), getContext().
  2. puli.json in the project root — the single source of truth for config, packages and bindings.
  3. .puli/ — generated state (discovery.json, path mappings, install info). Treat as a cache; never hand-edit.
  4. Puli\Manager\Api\Event\PuliEvents — the extension surface if you need to hook install/load lifecycle.
  5. Puli\Manager\Api\Config\Config, Api\Package\PackageManager, Api\Binding\BindingManager — the manager classes you will call in 90% of scripts.

First real use case — declare that a package’s assets are published under a URL path, then query them:

use Puli\Manager\Api\Puli;

$puli = Puli::create(base_path());           // bootstraps puli.json + .puli
$puli->getPackageManager()->loadPackages();

$puli->getBindingManager()->addBinding(
    \Puli\Discovery\Api\Binding\Binding::forPath('/app/public', '/app/assets')
);
$puli->getBindingManager()->saveBindings();

$resources = $puli->getDiscoveryManager()->find('/app/assets/**/*.css');

That is the whole shape of the package: mutate a manager, then call the matching save…() method. There is no auto-save, no facade, no Laravel integration of any kind.

Implementation Patterns

Wrap Puli behind a container singleton

Puli::create() parses and validates config; don’t call it per-request. Register it once:

// AppServiceProvider::register()
$this->app->singleton(\Puli\Manager\Api\Puli::class, function ($app) {
    return \Puli\Manager\Api\Puli::create($app->basePath());
});

Inject the manager you actually need (PackageManager, BindingManager, DiscoveryManager) rather than the facade, so unit tests can mock it.

Artisan command for edits

Most day-to-day mutations are one-off operations that belong in a command rather than HTTP:

$puli->getPackageManager()->installPackage('acme/assets', 'vendor/acme/assets', 'composer');
$puli->getPackageManager()->savePackages();

$puli->getDiscoveryManager()->clearDiscovery();  // invalidate cache after changes

Expose these as php artisan puli:bind {path} {query} and similar. This is the cleanest way to keep puli.json under version control while letting developers change it.

Composer script hooks

Package install/remove is naturally a Composer event. Add to composer.json:

"scripts": {
  "post-install-cmd": ["vendor/bin/puli build"],
  "post-update-cmd":  ["vendor/bin/puli build"]
}

For custom behavior, subscribe to PuliEvents::POST_INSTALL_PACKAGE / POST_REMOVE_PACKAGE on $puli->getContext()->getEventDispatcher() instead of reimplementing package discovery.

Read config through ConfigManager, not json_decode

Config is layered: root puli.json, each package’s puli.json, and extra.puli in composer.json. ConfigManager::getConfig() gives you the merged view with package-namespaced parameters (puli.asset-url-format, etc.):

$config = $puli->getConfigManager()->getConfig();

if ($config->hasParameter('puli.asset-url-format')) {
    $format = $config->getParameter('puli.asset-url-format');
}

Use cloneConfigFile() / addConfigFile() when you need to override layered config in a build, and always follow with saveConfig().

Discovery as a resource registry

DiscoveryManager::find($query) is the read side of bindings; findInDirectory() and findInPhpServer() are convenience variants. A typical asset publishing step is:

foreach ($puli->getDiscoveryManager()->find('/app/assets/**/*') as $resource) {
    copy((string) $resource->getFilesystemPath(), public_path(...));
}

Bindings are keyed by their path, so getBinding($path) / removeBinding($path) are the safe idempotent update primitives — check hasBinding() before adding to keep scripts re-runnable.

Servers and modules for URL generation

ServerManager and ModuleManager are how Puli exposes per-framework URL formats (puli/twig-extension, puli/symfony-bridge). In a Laravel app you would not use these — you would register a custom Server or simply call asset() yourself — but if you are porting a Symfony bundle integration, Server::forName(...)->getUrlFormat() is where the asset URL pattern lives.

Keep derived state out of the repository

Commit puli.json; gitignore .puli/. Rebuild it in CI with puli build, otherwise discovery.json will drift from the bindings and find() will return stale resources.

Gotchas and Tips

  • Abandoned and old. Last release January 2016, PHP 5.5/7.0 era. It will fight you on PHP 8 (composer resolution, deprecated constructs in transitive webmozart/* packages). Pin PHP or vendor the relevant classes if you must use it on modern runtimes.
  • This is not the CLI. puli/manager gives you the API; puli/cli gives you vendor/bin/puli. Documentation and examples online are almost all CLI-oriented, so translate commands into manager calls (puli bind → BindingManager::addBinding).
  • Mutations are not persisted automatically. Every addBinding/installPackage/setParameter needs the corresponding saveBindings()/savePackages()/saveConfig(). Forgetting the save is the number one silent failure — the in-memory state looks right for the rest of the request, then disappears.
  • Never hand-edit puli.json while a manager instance is live. The managers hold a parsed config; a later save…() will overwrite your manual edit. Route all writes through the API.
  • Bindings require an installed package and a resolvable path. Adding a binding for a path that doesn’t map to anything fails validation. Install the package (PackageManager::installPackage) before binding it.
  • Root package identity comes from composer.json. Puli::create() will fail confusingly if the root composer.json is missing or has no name. In a Laravel app that means composer.json must be at base_path().
  • Discovery cache is the source of “why is find() empty?” After any binding or package change call clearDiscovery() (and saveDiscovery() when the build is done). Conversely, deleting .puli/ forces a clean rebuild.
  • Config layering surprises. getConfigFile() is the root file, but getConfig() is merged. Overrides from package puli.json and extra.puli can shadow what you set at root — use isCloned()/cloneConfigFile() when a build needs a deterministic single-file view.
  • Error handling is exception-heavy. Puli\Manager\Api\Config\ConfigException, Api\Package\PackageException, Api\Binding\BindingException — catch these explicitly in commands and surface the message; they are precise and human-readable (better than the generic Puli CLI exit codes).
  • Thread/process safety is not handled. No file locking on puli.json. Do not run puli mutations in parallel CI jobs against the same checkout; serialize them.
  • Extension points are the event dispatcher and modules, not subclasses. Everything is final and built around PuliEvents. If you need custom behavior (e.g. copying assets on package install), add a listener on POST_INSTALL_PACKAGE rather than extending a manager.
  • No Laravel integration. No facade, no config publishing, no Mix/Vite bridge. Treat it as an external service in a Laravel app: one singleton, one or two Artisan commands, Composer hooks, and keep it out of the request path.
  • Real-world fit. Puli solved asset/resource discovery across Composer packages before Symfony’s and Laravel’s asset pipelines matured. If you are on a greenfield Laravel app today, the honest assessment is: don’t — use Vite/Mix and Composer’s extra block. Use puli/manager only when maintaining a legacy project that already has puli.json and a puli build step.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect