symfony/doctrine-bridge
Symfony Doctrine Bridge integrates Doctrine ORM and related libraries with Symfony components, providing seamless wiring for services, repositories, persistence, and tooling. Ideal for projects using Doctrine alongside Symfony’s DI container, validator, and other features.
Installation:
composer require symfony/doctrine-bridge
Ensure doctrine/orm and doctrine/doctrine-bundle are also installed for full functionality.
Configuration:
Add to config/packages/doctrine.yaml:
doctrine:
dbal:
driver: 'pdo_mysql'
url: '%env(DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
First Use Case:
Create an entity (e.g., src/Entity/User.php):
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private string $email;
// Getters/setters...
}
Use in a controller:
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
class UserController
{
public function __construct(private EntityManagerInterface $em) {}
public function index(): Response
{
$users = $this->em->getRepository(User::class)->findAll();
return new Response(json_encode($users));
}
}
Key Entry Points:
EntityManagerInterface (injected via dependency injection).Doctrine\ORM\EntityRepository (for custom queries).Validator (for entity validation).EntityManagerInterface into controllers/services:
public function __construct(private EntityManagerInterface $em) {}
ObjectManager for non-persistent operations (e.g., hydration):
$user = $this->em->getRepository(User::class)->find($id);
$this->em->getObjectManager()->detach($user); // Detach for non-persistent use
#[ORM\Entity(repositoryClass: CustomUserRepository::class)]
class User {}
class CustomUserRepository extends ServiceEntityRepository
{
public function findActiveUsers(): array
{
return $this->createQueryBuilder('u')
->where('u.isActive = :active')
->setParameter('active', true)
->getQuery()
->getResult();
}
}
$qb = $this->em->createQueryBuilder();
$qb->select('u', 'p')
->from(User::class, 'u')
->join('u.posts', 'p')
->where('p.published = :published')
->setParameter('published', true);
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
class User
{
#[ORM\Column(length: 180)]
#[Assert\Email]
private string $email;
}
$errors = $validator->validate($user);
if (count($errors) > 0) {
// Handle errors
}
#[ORM\Entity]
class Product
{
#[ORM\PrePersist]
public function setCreatedAt(): void
{
$this->createdAt = new \DateTime();
}
}
$this->em->beginTransaction();
try {
$this->em->persist($user);
$this->em->flush();
$this->em->commit();
} catch (\Exception $e) {
$this->em->rollBack();
throw $e;
}
#[AsEventListener(event: 'prePersist')]
public function prePersist(User $user): void
{
$user->setCreatedAt(new \DateTime());
}
$query = $this->em->createQuery('SELECT u FROM App\Entity\User u WHERE u.email LIKE :email');
$query->setParameter('email', '%@example.com');
$users = $query->getResult();
$sql = 'SELECT * FROM user WHERE email = ?';
$stmt = $this->em->getConnection()->prepare($sql);
$stmt->execute(['user@example.com']);
$users = $stmt->fetchAllAssociative();
$builder->add('user', EntityType::class, [
'class' => User::class,
'choice_label' => 'email',
]);
$form->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
// Custom logic
});
# config/packages/doctrine.yaml
orm:
second_level_cache:
enabled: true
region_directory: '%kernel.cache_dir%/doctrine'
$query->setCacheable(true);
$query->setCacheLifetime(3600);
composer require doctrine/doctrine-migrations-bundle
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Lazy Loading in Serialization:
LazyInitializationException.EntityManager::getObjectManager()->detach() or fetch associations eagerly:
$user = $this->em->find(User::class, $id);
$this->em->getObjectManager()->refresh($user); // Force load
Circular References in JSON:
JsonSerializable or Serializer with groups:
#[Groups(['user:read'])]
public function getPosts(): array { return $this->posts; }
$serializer->serialize($user, 'json', ['groups' => ['user:read']]);
Transaction Boundaries:
try-catch blocks and ensure flush() is called only after all operations:
$this->em->persist($entity);
$this->em->flush(); // Only after all persists/updates
Schema Updates:
ALTER TABLE) may fail silently or lock tables.php bin/console doctrine:schema:update --force
Entity Manager Scope:
EntityManager (e.g., from a subrequest).EntityManagerInterface and avoid static calls. For subrequests, use:
$this->em->getConnection()->getWrappedConnection();
Unique Constraints:
@UniqueEntity validator may not work as expected with custom repositories.#[UniqueEntity(fields: 'email', message: 'Email already taken')]
class User {}
// Repository must implement findByEmail()
public function findByEmail(string $email): ?User { ... }
Oracle Compatibility:
How can I help you explore Laravel packages today?