arturdoruch/doctrine-entity-manager-bundle
Installation:
composer require arturdoruch/doctrine-entity-manager-bundle
Register the bundle in config/bundles.php:
return [
// ...
ArturDoruch\DoctrineEntityManagerBundle\ArturDoruchDoctrineEntityManagerBundle::class => ['all' => true],
];
First Use Case:
Create a custom manager for an entity (e.g., User):
// src/Doctrine/UserManager.php
namespace App\Doctrine;
use App\Entity\User;
use ArturDoruch\DoctrineEntityManagerBundle\AbstractEntityManager;
class UserManager extends AbstractEntityManager
{
public function getRepository()
{
return $this->doGetRepository(User::class);
}
public function createUser(string $email, string $name): User
{
$user = new User();
$user->setEmail($email);
$user->setName($name);
$this->persist($user);
return $user;
}
}
Register as Service:
Add the service tag in config/services.yaml:
services:
App\Doctrine\UserManager:
tags: ['arturdoruch.doctrine_entity_manager']
Access the Manager: Inject the registry into a controller or service:
use ArturDoruch\DoctrineEntityManagerBundle\EntityManagerRegistry;
class UserController extends AbstractController
{
public function __construct(private EntityManagerRegistry $emRegistry) {}
public function createUser()
{
$userManager = $this->emRegistry->get(UserManager::class);
$user = $userManager->createUser('test@example.com', 'Test User');
// ...
}
}
Domain-Specific Managers:
Group CRUD operations for a single entity in a manager (e.g., ProductManager, OrderManager).
Example:
class ProductManager extends AbstractEntityManager
{
public function getRepository() { return $this->doGetRepository(Product::class); }
public function publish(Product $product)
{
$product->setPublishedAt(new \DateTime());
$this->persist($product);
}
}
Dependency Injection Between Managers:
Use getManager() to inject other managers (e.g., OrderManager in PaymentManager):
class PaymentManager extends AbstractEntityManager
{
private $orderManager;
public function initialize()
{
$this->orderManager = $this->getManager(OrderManager::class);
}
public function processPayment(Order $order, float $amount)
{
$this->orderManager->markAsPaid($order);
// ...
}
}
Transaction Management:
Leverage Doctrine’s transaction handling via the underlying EntityManager:
public function transferFunds(User $from, User $to, float $amount)
{
$this->getEntityManager()->beginTransaction();
try {
$from->subtractBalance($amount);
$to->addBalance($amount);
$this->persist($from);
$this->persist($to);
$this->getEntityManager()->commit();
} catch (\Exception $e) {
$this->getEntityManager()->rollBack();
throw $e;
}
}
Query DSL:
Use the repository’s methods (e.g., findBy, createQueryBuilder) within managers:
public function findActiveProducts(): array
{
return $this->getRepository()->findBy(['isActive' => true]);
}
Event Subscribers:
Attach Doctrine events (e.g., prePersist, postRemove) via the manager:
public function initialize()
{
$this->getEntityManager()->getEventManager()->addEventSubscriber(new ProductEventSubscriber());
}
Symfony Forms: Bind managers to form types for validation/processing:
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('save', SubmitType::class, [
'attr' => ['class' => 'btn btn-primary'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'manager' => $options['manager'], // Inject UserManager
]);
}
}
API Platform: Use managers in API resource classes for custom logic:
use ApiPlatform\Core\Annotation\ApiResource;
use App\Doctrine\UserManager;
#[ApiResource]
class User
{
public function __construct(private UserManager $manager) {}
public function prePersist()
{
$this->manager->generateApiToken($this);
}
}
Messenger Integration: Dispatch messages from managers for async processing:
use Symfony\Component\Messenger\MessageBusInterface;
class OrderManager extends AbstractEntityManager
{
public function __construct(private MessageBusInterface $bus) {}
public function placeOrder(Order $order)
{
$this->persist($order);
$this->bus->dispatch(new SendOrderConfirmationEmail($order));
}
}
CQRS Pattern: Separate read/write operations using managers:
// Command (Write)
class CreateProductCommandHandler
{
public function __construct(private ProductManager $manager) {}
public function __invoke(CreateProductCommand $command)
{
$product = $this->manager->createProduct($command->name, $command->price);
}
}
// Query (Read)
class GetProductQueryHandler
{
public function __construct(private ProductManager $manager) {}
public function __invoke(GetProductQuery $query)
{
return $this->manager->findProduct($query->id);
}
}
Circular Dependencies:
Avoid circular references between managers (e.g., AManager injecting BManager which injects AManager).
Fix: Use lazy initialization or refactor shared logic into a service.
EntityManager Lifecycle:
Managers are not scoped per-request by default. Ensure thread safety if used in async contexts (e.g., Messenger).
Fix: Use initialize() to set up per-request dependencies.
Repository Caching:
The doGetRepository() method caches repositories. Clear the cache if repositories change dynamically.
Fix: Call $this->clearRepositoryCache() if needed (exposed via AbstractEntityManager).
Transaction Isolation:
Managers share the same EntityManager as their parent bundle. Long-running operations may lock tables.
Fix: Use setLockMode() or optimize queries.
Service Tagging:
Forgetting to tag managers with arturdoruch.doctrine_entity_manager will prevent them from being registered.
Fix: Verify tags in debug:container or debug:autowiring.
Registry Issues:
If get() returns null, check:
debug:container).config/bundles.php.EntityManager Errors:
Use getEntityManager()->getConnection()->getDatabasePlatform() to debug platform-specific issues (e.g., MySQL vs. PostgreSQL).
Lazy Loading:
Enable debug:orm:proxy to verify proxy classes are generated:
php bin/console debug:orm:proxy
Custom EntityManager Names:
Override getEntityManagerName() to use a specific connection:
protected function getEntityManagerName(): string
{
return 'read_connection'; // Uses the 'read_connection' Doctrine connection
}
Shared EntityManagers:
By default, all managers share the same EntityManager. To isolate:
# config/packages/doctrine.yaml
doctrine:
dbal:
connections:
default: { url: '%env(DATABASE_URL)' }
read: { url: '%env(READ_DATABASE_URL)' }
Then override getEntityManagerName() in your manager.
Proxy Generation:
Ensure doctrine/orm is configured for proxy generation:
doctrine:
orm:
proxy_dir: '%kernel.cache_dir%/doctrine/orm/Proxies'
proxy_namespace: App\Proxy
generate_proxies: true
initialize() to set up dependencies or listeners:
public function initialize
How can I help you explore Laravel packages today?