75 phút
Doctrine ORM và Entities
Entity với Attributes
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[ORM\HasLifecycleCallbacks]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 100)]
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 100)]
private string $name;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private string $password;
#[ORM\OneToMany(mappedBy: 'author', targetEntity: Post::class, cascade: ['persist', 'remove'])]
private Collection $posts;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->posts = new ArrayCollection();
$this->createdAt = new \DateTimeImmutable();
}
#[ORM\PrePersist]
public function onPrePersist(): void
{
$this->createdAt = new \DateTimeImmutable();
}
// Getters và Setters...
public function getId(): ?int { return $this->id; }
public function getName(): string { return $this->name; }
public function setName(string $name): static { $this->name = $name; return $this; }
public function getEmail(): string { return $this->email; }
public function setEmail(string $email): static { $this->email = $email; return $this; }
public function getUserIdentifier(): string { return $this->email; }
public function getRoles(): array { return array_unique([...$this->roles, 'ROLE_USER']); }
public function getPassword(): string { return $this->password; }
public function setPassword(string $p): static { $this->password = $p; return $this; }
public function eraseCredentials(): void {}
}
Relations
ManyToOne / OneToMany
#[ORM\Entity]
class Post
{
#[ORM\ManyToOne(inversedBy: 'posts')]
#[ORM\JoinColumn(nullable: false)]
private User $author;
// ...
}
ManyToMany
#[ORM\Entity]
class Post
{
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'posts')]
#[ORM\JoinTable(name: 'post_tags')]
private Collection $tags;
}
Repository
<?php
namespace App\Repository;
use App\Entity\User;
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 findByEmail(string $email): ?User
{
return $this->findOneBy(['email' => $email]);
}
/** @return User[] */
public function findActiveUsers(): array
{
return $this->createQueryBuilder('u')
->where('u.active = :active')
->setParameter('active', true)
->orderBy('u.createdAt', 'DESC')
->getQuery()
->getResult();
}
public function search(string $term): array
{
return $this->createQueryBuilder('u')
->where('u.name LIKE :term OR u.email LIKE :term')
->setParameter('term', "%$term%")
->setMaxResults(20)
->getQuery()
->getResult();
}
}
Migrations
# Generate migration
php bin/console make:migration
# Run migrations
php bin/console doctrine:migrations:migrate
# Check status
php bin/console doctrine:migrations:status
# Rollback
php bin/console doctrine:migrations:migrate prev
Migration file example
final class Version20240101120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create users table';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(180) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL
)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE users');
}
}
Fixtures (Test data)
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixtures;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AppFixtures extends Fixtures
{
public function __construct(
private UserPasswordHasherInterface $hasher
) {}
public function load(ObjectManager $manager): void
{
for ($i = 1; $i <= 10; $i++) {
$user = new User();
$user->setName("User $i");
$user->setEmail("user$i@example.com");
$user->setPassword($this->hasher->hashPassword($user, 'password'));
$manager->persist($user);
}
$manager->flush();
}
}
php bin/console doctrine:fixtures:load
Service sử dụng Repository
<?php
namespace App\Service;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserService
{
public function __construct(
private UserRepository $repository,
private EntityManagerInterface $em,
private UserPasswordHasherInterface $hasher
) {}
public function create(string $name, string $email, string $password): User
{
if ($this->repository->findByEmail($email)) {
throw new \DomainException('Email already exists');
}
$user = new User();
$user->setName($name);
$user->setEmail($email);
$user->setPassword($this->hasher->hashPassword($user, $password));
$this->em->persist($user);
$this->em->flush();
return $user;
}
public function update(int $id, array $data): User
{
$user = $this->repository->find($id)
?? throw new \DomainException('User not found');
if (isset($data['name'])) $user->setName($data['name']);
if (isset($data['email'])) $user->setEmail($data['email']);
$this->em->flush();
return $user;
}
public function delete(int $id): void
{
$user = $this->repository->find($id)
?? throw new \DomainException('User not found');
$this->em->remove($user);
$this->em->flush();
}
}
Bài tập thực hành
Hãy tạo entity Product với CRUD operations!