Installation:
composer require atheon/doctrine-bundle
(Note: This appears to be a repackaged version of doctrine/doctrine-bundle. Verify if this is intentional or a fork. The official package is doctrine/doctrine-bundle.)
Enable the Bundle:
Add to config/bundles.php:
return [
// ...
Atheon\DoctrineBundle\AtheonDoctrineBundle::class => ['all' => true],
];
Configure Database:
Update config/packages/doctrine.yaml (or create it):
doctrine:
dbal:
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: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
First Use Case:
Create an entity (e.g., src/Entity/User.php):
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: "App\Repository\UserRepository")]
class User
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $name;
Run migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Entity Management:
$user = new User();
$user->setName('John Doe');
$entityManager->persist($user);
$entityManager->flush();
Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository for custom queries:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findByName(string $name): array
{
return $this->createQueryBuilder('u')
->where('u.name = :name')
->setParameter('name', $name)
->getQuery()
->getResult();
}
}
Database Abstraction (DBAL):
Connection for raw SQL or schema operations:
$connection = $this->getConnection();
$users = $connection->fetchAllAssociative('SELECT * FROM user');
php bin/console doctrine:schema:update --force
Query Builder:
$qb = $entityManager->createQueryBuilder();
$qb->select('u')
->from(User::class, 'u')
->where('u.name LIKE :name')
->setParameter('name', '%John%');
Lifecycle Callbacks:
#[ORM\PrePersist]
public function setCreatedAt(): void
{
$this->createdAt = new \DateTime();
}
Dependency Injection:
EntityManagerInterface or Doctrine service:
public function __construct(private EntityManagerInterface $em) {}
Symfony Forms:
$form = $this->createForm(UserType::class, $user);
EntityType for dropdowns:
$builder->add('role', EntityType::class, [
'class' => Role::class,
'choice_label' => 'name',
]);
Validation:
#[ORM\Entity, Validation\Constraints\Valid]
class User { ... }
APIs (API Platform):
#[ApiResource]
class User { ... }
Testing:
DatabaseTestCase for fixtures:
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class UserTest extends KernelTestCase
{
private EntityManagerInterface $em;
protected function setUp(): void
{
self::bootKernel();
$this->em = self::$kernel->getContainer()->get('doctrine')->getManager();
}
}
Caching:
doctrine.yaml for query caching:
orm:
metadata_cache_driver: apcu
query_cache_driver: apcu
result_cache_driver: apcu
Proxy Classes:
php bin/console cache:clear
auto_generate_proxy_classes is true and proxy_dir is writable.Naming Conflicts:
user vs User) can cause issues on some DBs.naming_strategy (e.g., doctrine.orm.naming_strategy.underscore).Lazy Loading:
N+1 queries in loops:
foreach ($users as $user) {
echo $user->getPosts()->count(); // Triggers N+1 queries
}
fetch="EAGER" or join in queries.Migrations:
doctrine:schema:validate before migrations.Transaction Management:
$entityManager->beginTransaction();
// ... operations
$entityManager->commit(); // Don't forget this!
Circular References:
inversedBy/mappedBy:
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: "roles")]
private Collection $users;
Deprecated Features:
doctrine:generate:entities (deprecated in favor of make:entity).Query Logging:
Enable in config/packages/dev/doctrine.yaml:
dbal:
logging: true
profiling: true
View queries in the Profiler (/_profiler).
Doctrine Profiler:
symfony/web-profiler-bundle for detailed ORM/DBAL stats.Common Errors:
auto_mapping and prefix in doctrine.yaml.@Column or schema updates.DATABASE_URL in .env.Custom DQL Functions:
doctrine.yaml:
orm:
dql:
string_functions:
CONCAT_WS: App\Doctrine\StringFunctions::concatWs
Event Subscribers:
namespace App\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
class UserSubscriber implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return ['prePersist'];
}
public function prePersist(LifecycleEventArgs $args): void
{
$entity = $args->getEntity();
if ($entity instanceof User) {
$entity->setCreatedAt(new \DateTime());
}
}
}
Custom Repository Factories:
doctrine.yaml:
orm:
repository_factory: App\Doctrine\CustomRepositoryFactory
Database Platform Extensions:
Doctrine\DBAL\Platforms\AbstractPlatform for custom SQL dialects.Fixtures:
How can I help you explore Laravel packages today?