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

Bridge Phpdi Symfony Laravel Package

teknoo/bridge-phpdi-symfony

Bridges PHP-DI and Symfony, enabling Symfony applications to use PHP-DI’s dependency injection container for service definitions, autowiring, and container interoperability. Helpful for projects adopting PHP-DI or migrating DI setup while keeping Symfony conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require teknoo/bridge-phpdi-symfony

Constraints to check before you commit to it: php-di/php-di ^7.1.1, php-di/invoker ^2.2.0, symfony/{dependency-injection,http-kernel,config} at ^6.4.24 || ^7.4 || ^8.1, and PHP 8.4+. The PHP 8.4 floor is a hard blocker on older stacks.

Register the bundle in config/bundles.php (the bundle class lives in src/; open that directory first if the class name isn't obvious from the README):

return [
    // ...
    Teknoo\DI\SymfonyBridge\BridgeBundle::class => ['all' => true],
];

Write PHP-DI definitions as ordinary PHP-DI 7 config files:

<?php
// config/di/services.php
use function DI\create;
use function DI\get;

return [
    'app.mailer' => create(App\Mailer::class)->constructor(get('app.transport')),
    'app.transport' => create(App\Transport::class),
    App\Mailer::class => get('app.mailer'), // becomes a Symfony alias
];

Point the bundle at them

# config/packages/di_bridge.yaml
di_bridge:
    definitions:
        - '%kernel.project_dir%/config/di/services.php'
    compilation_path: '%kernel.cache_dir%/phpdi'
    enable_cache: false
    import:
        - 'logger'   # Symfony service ids to alias into PHP-DI

First useful use case: you have a library or legacy module already wired with PHP-DI, and you want it inside a normal Symfony app without a custom Kernel. Put the PHP-DI definitions in the config above, then inject the result wherever you like — each PHP-DI entry is exported as a Symfony service whose factory is the bridge, so a controller can just type-hint it:

final class SignupController
{
    public function __construct(private App\Mailer $mailer) {}
}

Where to look first (in this order):

  1. src/DependencyInjection/Configuration.php — the authoritative config tree; don't trust examples over this file.
  2. The README section "How PHP-DI entries are exported into Symfony" — the rules for what becomes a service, an alias, or a parameter.
  3. src/Container/BridgeBuilder.php — the compile-time logic that actually does the export.
  4. tests/FunctionalTest/Fixtures/config/ — real, working YAML + di*.php pairs; the fastest way to see a valid setup.

Implementation Patterns

Symfony-first, PHP-DI for a legacy module. Keep controllers, commands and listeners as normal Symfony services. Confine PHP-DI to the subsystem that already uses it. Resist the temptation to define everything in PHP-DI — you lose Symfony's autoconfiguration, tagged collectors, and debug:container legibility for anything defined there.

Reverse direction with import. PHP-DI definitions can't see Symfony services by default. di_bridge.import creates aliases in the PHP-DI container pointing at Symfony entries. So a PHP-DI service that needs the Symfony logger:

di_bridge:
    import:
        - 'logger'
        - 'doctrine.orm.entity_manager'
// config/di/services.php — now resolvable
'legacy.reporter' => create(App\Reporter::class)->constructor(get('logger')),

Import aggressively but narrowly: every imported id is one more coupling point between the two containers.

Parameters flow one way. Symfony parameters are exposed to PHP-DI automatically, so get('kernel.environment') or %kernel.debug%-style values work inside definitions. The reverse is not true — a PHP-DI entry that is a scalar gets exported as a Symfony parameter, but don't build a dependency graph on that; treat scalars in PHP-DI as configuration, not as a shared parameter bus.

Ordered definitions for environment overrides. Each di_bridge.definitions entry accepts a priority; highest loads first. Use this for a clean prod/dev split instead of duplicating files:

di_bridge:
    definitions:
        - { path: '%kernel.project_dir%/config/di/services.php', priority: 10 }
        - { path: '%kernel.project_dir%/config/di/services.%kernel.environment%.php', priority: 20 }

Note that highest loads first, which is the opposite of what most people assume about priority numbers — verify against BridgeBuilder before you rely on override semantics.

Extensions for cross-cutting bridge setup. Implement Teknoo\DI\SymfonyBridge\Extension\ExtensionInterface and register it in di_bridge.extensions either by class name or by Symfony service id (with optional priority). Good uses: adding a definitions directory based on a feature flag, bulk-aliasing a set of Symfony ids into PHP-DI, or asserting that required parameters exist. Use an extension rather than a compiler pass — the bridge gives you the PHP-DI builder at the right moment.

Prefer the service-id form of extensions when the extension itself has dependencies. A class-name extension is instantiated directly, so it can't take constructor arguments from the container. If it needs the container, register it as a Symfony service and reference the id.

Compile once, ship the cache. Set compilation_path inside %kernel.cache_dir% and let your deploy run cache:clear/cache:warmup. In dev, this also gives you a single directory to delete when things look stale.

Testing approach. Because the export happens at Symfony compile time, unit-testing the bridge in isolation is mostly futile. Follow the package's own model: boot a real kernel in a functional test (AbstractFunctionalTests::createKernel('file.yml') in this repo's tests is a good template), assert on the resolved services, and clear the cache between runs. If you need a test double for a PHP-DI entry, add a higher-priority definitions file for test rather than mocking the container.

Feature-flag by swapping definitions files, not by conditionally registering the bundle. The bundle's whole contract is compile-time; toggling it at runtime leaves you with a stale compiled container.

Gotchas and Tips

Everything is resolved at container compile time. Editing a di.php file without clearing the Symfony cache does nothing. If a change "isn't taking effect," it's almost always the cache. In dev this is painful enough that you should wire cache:clear into your watch loop.

Two caches, not one. Symfony's container cache and (when enable_cache: true) PHP-DI's definitions cache are separate. Clear both. The PHP-DI cache also sits behind APCu.

enable_cache requires APCu. On CLI — which includes bin/console and every test run — you need apc.enable_cli=1 or the cache quietly does nothing (the package's own tests are skipped without it). In a container or CI image without APCu, leave enable_cache: false rather than debugging phantom behaviour.

Beware duplicate instances. If a class is registered both as a Symfony service and as a PHP-DI entry, you get two objects with separate state. Singleton assumptions break in ways that are very hard to trace. Pick one container per class and, where a boundary must be crossed, use di_bridge.import to alias rather than redefining.

Exported services are private. They're normal Symfony services, so $container->get('app.mailer') from a controller will fail in Symfony 6+/7+. Inject them. For inspection use bin/console debug:container --show-private and look for services whose factory is the bridge.

Id collisions are silent-ish. A PHP-DI entry whose id matches an existing Symfony service id is a conflict resolved by load order, not by an error you'll necessarily notice. Namespace your PHP-DI ids (app.*, legacy.*) and keep class-name ids for the aliases.

PHP-DI autowiring can flood the container. If you lean on PHP-DI's autowiring, every autowired entry becomes a Symfony definition. On a large codebase this inflates the compiled container and slows warmup. Define explicitly for anything crossing into Symfony; let autowiring stay internal to PHP-DI.

Values that can't resolve at compile time. Closures and environment-dependent factories are the usual source of "works in PHP-DI alone, fails through the bridge." The bridge has to extract definitions (extractDefinition()) to export them. If a definition can't be extracted, it can't be exported — keep runtime-dependent construction inside the factory body, not in the definition itself.

Extensions-by-service-id depend on prepend() timing. Symfony hands load() a throwaway container, so the extension resolves service-id-declared extensions during prepend() against the real application container. Consequence: the extension service must already be registered at that point. Register it from your own bundle's prepend()/extension, not from services.yaml loaded later, or the bridge will silently miss it.

compilation_path must be writable at container build time. On read-only filesystems (some serverless/PaaS targets) omit it and accept the in-memory path, or pre-warm the cache during the build step.

Don't build on internals. Bridge, BridgeBuilder, Container, CompiledContainer and ContainerDefinitionTrait are implementation. The stable surface is: the di_bridge config keys, the ExtensionInterface, and the exported Symfony services. With two GitHub stars and a single active maintainer, pin an exact version in composer.json and read the src/ you depend on before upgrading.

Debugging checklist, in order: (1) clear both caches; (2) debug:container --show-private | grep for the entry; (3) confirm the definitions file is in di_bridge.definitions with the priority you expect; (4) confirm the entry is extractable (not a raw closure); (5) confirm it hasn't been shadowed by a same-id Symfony service; (6) only then read BridgeBuilder.

APCu + Xdebug in CI. The package's own make test needs both extensions. Expect your CI image to need apcu and apc.enable_cli=1 if you mirror their test setup; otherwise run --no-coverage without Xdebug and skip the cache-dependent tests.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core