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

Doctrine Bridge Laravel Package

symfony/doctrine-bridge

Symfony Doctrine Bridge connects the Doctrine project with Symfony components, supplying form types, validators, security user providers, event listeners, and other integration code for Doctrine-backed Symfony apps. A core part of the Symfony framework.

View on GitHub
Deep Wiki
Context7

Getting Started

symfony/doctrine-bridge is not a library you adopt; it is the glue layer that lets Doctrine talk to Symfony's Validator, Form, Security, Messenger, Serializer, PropertyInfo, Lock, Cache, and Session components. As a Laravel developer the first question is not "how do I install this?" but "why is it in my composer.lock at all?" — run:

composer why symfony/doctrine-bridge

In a normal Laravel app it shows up only when something dragged Doctrine in (usually doctrine/doctrine-bundle / doctrine/orm, or a package that depends on Symfony Form or Security with Doctrine integration). You almost never composer require symfony/doctrine-bridge directly: its version is pinned to the Symfony minor (8.1.x with Symfony 8.1, 7.4.x with 7.4), and DoctrineBundle is the thing that actually wires the services.

Where to look first, in order of day-to-day usefulness:

  1. Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity + UniqueEntityValidator — the only class most people touch directly.
  2. Symfony\Bridge\Doctrine\Security\User\EntityUserProvider — Doctrine-backed auth.
  3. Symfony\Bridge\Doctrine\Messenger\* — doctrine:// queue transport and its middleware.
  4. Symfony\Bridge\Doctrine\Form\Type\EntityType and DoctrineOrmTypeGuesser — Symfony Forms only.
  5. Symfony\Bridge\Doctrine\Types\* — UuidType, UlidType, DayPointType, TimePointType.
  6. Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor — metadata for Serializer/Validator.
  7. Symfony\Bridge\Doctrine\ManagerRegistry / ContainerRepositoryFactory — repositories as services.
  8. Symfony\Bridge\Doctrine\Middleware, SchemaListener, CacheWarmer\ProxyCacheWarmer — infrastructure.

First real use case (works in any framework, including Laravel, once you have an EntityManagerInterface):

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Validation;

#[UniqueEntity(fields: ['email'], message: 'This email is already registered.')]
class User
{
    public function __construct(
        public string $email,
    ) {}
}

$validator = Validation::createValidatorBuilder()
    ->enableAttributeMapping()
    ->getValidator();

// Requires the UniqueEntityValidator to be registered with a ManagerRegistry,
// which DoctrineBundle does for you in Symfony; in Laravel you would wire it manually.

If you are not already running Doctrine ORM, that snippet is the point at which you should stop and ask whether Eloquent's unique:users,email rule is enough. Usually it is. The bridge only pays off when you have committed to Doctrine and to Symfony components.

Implementation Patterns

Validation: UniqueEntity over a Doctrine repository. This is the bridge's most portable feature. It supports composite keys, ignoreNull, errorPath, and a custom repositoryMethod returning a scalar count:

#[UniqueEntity(
    fields: ['tenant', 'slug'],
    ignoreNull: true,
    repositoryMethod: 'countByTenantAndSlug',
    errorPath: 'slug',
)]
class Page {}

Keep repositoryMethod cheap and index-backed; it runs on every validate() call. With Uuid/Ulid identifier objects, upgrade to at least 7.2.7/7.3 — earlier versions compared Stringable IDs incorrectly.

Security: EntityUserProvider. Wire it explicitly when you are not on the full Symfony security bundle:

use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider;

$provider = new EntityUserProvider($managerRegistry, User::class, 'email');
$user = $provider->loadUserByIdentifier('[email protected]');

Property-based loading is fine until you need case-insensitive or tenant-scoped lookups; then implement Symfony\Component\Security\Core\User\UserLoaderInterface and hand it to the provider. Note the 7.3.4 fix making providers pass request attributes to the loader — custom loaders that read attributes need that patch.

Messenger: Doctrine as a queue. Instead of Redis/SQS you can keep jobs in the same database:

framework:
  messenger:
    transports:
      async:
        dsn: 'doctrine://default?table_name=messenger_messages&queue_name=default&redeliver_timeout=3600&auto_setup=true'

Pair it with the bridge middleware, which is where most correctness lives:

framework:
  messenger:
    buses:
      messenger.bus.default:
        middleware:
          - doctrine_ping_connection     # reconnect after idle
          - doctrine_transaction         # wrap handler in a DB transaction
          - doctrine_close_connection    # optional: release connection after handling

In Laravel terms this is the closest analogue to database queue driver, but with real transaction semantics around the handler. auto_setup: true creates the table on first use — convenient locally, a footgun on databases with restricted DDL permissions.

Infrastructure without ORM. Several bridge classes need only DBAL, which makes them usable in Laravel against an existing connection:

  • Symfony\Bridge\Doctrine\Lock\DoctrineDbalStore (DSN doctrine://default) for cross-process locks; DoctrineDbalPostgreSqlStore gives advisory locks.
  • Symfony\Bridge\Doctrine\Cache\DoctrineDbalAdapter for a shared cache table.
  • Symfony\Bridge\Doctrine\HttpFoundation\DoctrineDbalSessionHandler for sessions.

These are genuinely useful if you already have a DBAL Connection and want Symfony-component behaviour without adding Redis.

Repositories as services. ContainerRepositoryFactory lets Doctrine instantiate repositories from the DI container, so constructor injection works:

class InvoiceRepository extends EntityRepository
{
    public function __construct(ManagerRegistry $registry, private ClockInterface $clock)
    {
        parent::__construct($registry, Invoice::class);
    }
}

Register with doctrine.orm.entity_manager + doctrine.repository_service tags. Without this, repositories are new-ed by Doctrine and silently lose dependencies.

Custom Doctrine types. Register UuidType/UlidType (or the newer DayPointType/TimePointType) via doctrine.dbal.types. For Uid types, the entity loader also needs the matching fix (7.3.10/7.4.4): without it, find() with a Uuid argument fails to convert.

Long-running workers. Symfony's IdleConnectionListener only runs on the master request (fixed in 7.2.9/7.3.2) and now closes all expired connections. In queue workers, still call $em->clear() after each job and rely on doctrine_ping_connection rather than assuming the connection survived.

Schema tooling. schema_filter is honoured by schema listeners from 7.4.6 onward. If you use schema_filter to hide tables owned by another app, older versions will happily emit DROP TABLE for them in doctrine:migrations:diff.

Gotchas and Tips

  • Never require the bridge directly in a Laravel project. Let doctrine/doctrine-bundle constrain it. A manual requirement drifts out of sync with the Symfony minor and manifests as bizarre "method does not exist" errors inside DoctrineBundle.
  • Match Symfony minors exactly. The 7.4/8.0/8.1 changelogs are dominated by DBAL compatibility churn (TableEditor migration, DBAL 4.5 restore, <4.5 fixes). Mixing bridge 8.1 with DBAL 3.x is not supported; run composer update --with-all-dependencies doctrine/dbal together with Symfony.
  • Two ORMs, two identity maps. If you run Eloquent and Doctrine side by side, a Doctrine transaction does not cover Eloquent writes and vice versa. Do not attempt distributed correctness; pick one writer per aggregate, or drop Doctrine.
  • Proxy directory pain. ProxyCacheWarmer now filters non-.php files (7.4.15/8.1.2), which specifically fixed cache:clear dying when editors drop .gitkeep, .DS_Store, or backups into the proxy dir. Still: keep the proxy dir writable, set auto_generate_proxy_classes: false in production, and run doctrine:ensure-production-settings in CI.
  • Lazy listeners are subtly stateful. Several bugs landed in 7.4.17/8.1.5 — losing listeners when a lazy listener registers another listener, removing lazy listeners by object, lazy resolution with native lazy objects. If you use lazy: true, pin to those patch levels; debugging symptoms look like "my subscriber randomly doesn't fire".
  • Inherited-property mapping. Auto-mapping strategies were ignored on inherited properties until 7.4.17/8.1.5. If mappings live on an abstract base entity and #[ORM\Column] on the child is being skipped, this is the cause.
  • sameDatabaseChecker temporary tables. Historically fragile: Oracle reserved-word rename, PDO path, catching driver exceptions, "session table" detection. If doctrine:query:sql or lock/session setup fails oddly on Oracle or PDO, check that patch level before writing workarounds.
  • UniqueEntity with Stringable IDs, composite keys, and ignoreNull — each has had a regression. Validate with an integration test that hits the real repository, not a mocked one; the validator's behaviour depends on Doctrine metadata and the actual getResult() shape.
  • Removed in 8.0, deprecated in 7.4: AbstractDoctrineExtension (custom bundles extending it must move to AbstractBundle/PrependExtensionInterface), auto-mapping entities to controller arguments (declare EntityValueResolver mapping or use explicit #[MapEntity]), and generic constraint option handling. If you are upgrading from 7.x, grep for these before the bump.
  • Debugging toolkit. Use the Doctrine data collector / profiler for query counts, bin/console doctrine:query:sql for sanity checks, --debug for verbose listener registration, and EntityManager::getUnitOfWork()->getScheduledEntityInsertions() when changes vanish. For "why is this proxy uninitialized", $em->getUnitOfWork()->isInIdentityMap($entity) and Doctrine\ORM\Tools\Debug style checks beat var_dump.
  • Extension points worth knowing: custom DBAL types, custom DQL functions, custom ManagerRegistry, custom RepositoryFactory, doctrine.event_listener / doctrine.event_subscriber tags, and (ORM side) #[AsEntityListener]. Most "the bridge won't let me…" problems are solved by registering one of these rather than patching the bridge.
  • Verdict for a Laravel shop: treat this as a transitive dependency you audit, not a dependency you adopt. The high-value pieces (UniqueEntity, DBAL lock/cache/session stores, Messenger doctrine:// transport) are worth reaching for only once Doctrine is already in the stack. If Eloquent is your ORM, the bridge's surface area relevant to you is approximately zero — and that is the correct assessment, not a criticism.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource