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

Composed Laravel Package

joshdifabio/composed

Composed is a Composer plugin for projects split across multiple composer.json files. It merges their dependency requirements into a single graph, so you can install and autoload packages from separate directories without juggling each manifest by hand.

Deep Wiki
Context7

Getting Started

joshdifabio/composed gives you a read-only runtime view of a project's composer.json and the composer.json of everything installed via composer.lock. It is useful when your application needs to introspect itself or its dependencies at runtime without shelling out to composer show.

Install

composer require joshdifabio/composed

The last tagged release is from February 2016, so treat this as a legacy dependency. Check the package's composer.json constraints and run your test suite on the PHP version you deploy before adopting it. If it conflicts with your Composer version or PHP 8, isolate it behind your own small adapter interface.

Where to look first

  1. README.md for the current entry-point class and method names — they are the most likely thing to have drifted from older blog examples.
  2. src/ for the package/loader class. The central concept is a Package object representing a directory containing a composer.json.
  3. tests/ for working examples of parsing both root and dependency packages; the fixtures show exactly how lock data is interpreted.

First use case: identify the host application

use Composed\Package;

$root = Package::fromDirectory(__DIR__ . '/..');

$name    = $root->getConfig('name');        // e.g. "acme/shop"
$version = $root->getConfig('version');
$extra   = $root->getConfig('extra', []);  // custom runtime configuration

A realistic first win is reading a value from the root package's extra block instead of duplicating it in .env or a separate config file.

Implementation Patterns

1. Root-package introspection for application metadata

Use the root package as the single source of truth for values that already belong in composer.json:

$root = Package::fromDirectory(base_path());

return [
    'name'    => $root->getConfig('name'),
    'version' => $root->getConfig('version', 'dev'),
    'extra'   => $root->getConfig('extra', []),
];

Good candidates: feature flags in extra, support/contact metadata, build identifiers, and package-name-based cache key prefixes.

2. Discovering installed packages and their configuration

Iterate the resolved dependency set (from composer.lock) to find packages that advertise capabilities in extra:

$root = Package::fromDirectory(base_path());

foreach ($root->getInstalledPackages() as $package) {
    $config = $package->getConfig();

    if (isset($config['extra']['my-app']['plugin'])) {
        $plugins[] = [
            'name'    => $config['name'],
            'class'   => $config['extra']['my-app']['plugin'],
            'version' => $package->getVersion(),
        ];
    }
}

This is the classic plugin-registry pattern: packages opt in via extra, and your application discovers them at boot. It avoids hard-coding a list of first-party packages and works with third-party packages installed later.

3. Answering "why is this installed?"

Because the package exposes dependency configs, you can inspect require / require-dev across installed packages to explain transitive dependencies:

$target = 'psr/log';

$dependents = array_filter(
    $root->getInstalledPackages(),
    function ($package) use ($target) {
        $requires = $package->getConfig('require', []);
        return array_key_exists($target, $requires);
    }
);

Useful in an internal diagnostic command or a CI audit step, not in a hot request path.

4. Diagnostics command in a framework

Wrap the library in a console command rather than calling it from controllers:

class ComposerDiagnoseCommand extends Command
{
    protected function configure()
    {
        $this->setName('app:composer:diagnose');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $root = Package::fromDirectory(base_path());

        $output->writeln(sprintf(
            '<info>%s</info> @ %s',
            $root->getConfig('name'),
            $root->getConfig('version', 'unknown')
        ));

        foreach ($root->getInstalledPackages() as $package) {
            $output->writeln(sprintf(
                ' - %s %s',
                $package->getConfig('name'),
                $package->getVersion()
            ));
        }
    }
}

5. Adapter + cache boundary

Because the library reads from disk and is unmaintained, keep it behind a narrow interface:

interface ProjectMetadata
{
    public function rootConfig(string $key, $default = null);
    public function installedPackages(): array;
}

Implement it once with Composed, and cache the parsed result in your DI container or a PSR-16 cache with a key derived from the composer.lock hash. The composer configuration does not change during a request, so parse it once.

$lockHash = md5_file(base_path('composer.lock'));
$metadata = $cache->remember("composer-meta:$lockHash", 3600, function () {
    return (new ComposedProjectMetadata(base_path()))->all();
});

6. Conditional service-provider registration (Laravel-style)

Use dependency presence as a feature switch instead of hard-coding environment checks:

$installed = collect($metadata->installedPackages())->pluck('name');

if ($installed->contains('laravel/horizon')) {
    $this->app->register(HorizonServiceProvider::class);
}

Keep this to optional integrations; core application wiring should still be explicit.

7. Monorepo and path-repository awareness

In a monorepo, each package directory has its own composer.json. Instantiate one Package per directory rather than assuming a single root:

foreach (glob(__DIR__ . '/packages/*/composer.json') as $file) {
    $package = Package::fromDirectory(dirname($file));
    // ...
}

Do not walk up from getcwd() implicitly — always pass an explicit absolute directory.

Gotchas and Tips

  • Maintenance status is the biggest risk. Last release in 2016 means no PHP 8 type-safety work and no Composer 2-era changes. Pin a version, add a contract test, and keep the adapter boundary so you can replace it with direct composer.json/composer.lock decoding later. The fallback is ~30 lines of json_decode if needed.
  • A composer.lock must exist. Installed-package introspection depends on lock data. In libraries consumed by other projects, the "root" is the host application, not your package. Guard against a missing lock file and degrade gracefully.
  • Lock format differences. Composer 1 and Composer 2 lock files share the packages / packages-dev structure, but content-hash and metadata differ. If you compare or cache on lock contents, hash the raw file rather than trusting parsed fields.
  • Dev dependencies may be omitted in production. If you rely on require-dev packages appearing in the installed set, test against composer install --no-dev — those entries will not be present at runtime.
  • Aliases, replace, provide, and platform packages are not dependency resolution. The library parses configuration; it does not resolve versions or explain conflicts. For "who requires X", inspect declared require entries and accept that aliases/replacements can make the answer approximate.
  • Always pass an explicit directory. Package::fromDirectory(...) with a path derived from __DIR__, base_path(), or a configured project root avoids surprises when the working directory changes (queue workers, PHP-FPM, cron).
  • Verify method names against the installed release. The API surface (fromDirectory, getConfig, installed-package accessors) is small but old; IDE completion and composer show -a joshdifabio/composed are more reliable than copied snippets. Wrap access in your adapter on day one.
  • JSON parse errors are configuration errors. A trailing comma or BOM in a dependency's composer.json can surface as an exception. Catch parse failures per package when scanning third-party packages so one malformed dependency does not break your whole registry.
  • Do not expose the raw config over HTTP. Composer configuration often contains repository URLs, internal package names, and occasionally credentials in config/repositories. Return only the fields your application intentionally consumes.
  • Version reporting is only as good as the package metadata. Root packages often have no version field (it comes from VCS tags at install time); fall back to dev or your own build identifier.
  • Extension point: your adapter, not the library. The library is intentionally minimal. If you need caching, filtering, or normalized output, implement it in your wrapper. Avoid forking unless you are prepared to own Composer lock parsing.
  • Performance. Parsing is filesystem-bound and cheap but not free. Do it once at bootstrap or in a command, never per request or inside loops over packages.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package