实体管理 #

一、实体CRUD #

1.1 创建实体 #

php
<?php

namespace App\Service;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;

class UserService
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {}

    public function create(array $data): User
    {
        $user = new User();
        $user->setName($data['name']);
        $user->setEmail($data['email']);
        $user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
        $user->setActive(true);
        $user->setCreatedAt(new \DateTimeImmutable());

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return $user;
    }
}

1.2 查询实体 #

php
<?php

namespace App\Service;

use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;

class UserService
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private UserRepository $userRepository
    ) {}

    public function find(int $id): ?User
    {
        return $this->userRepository->find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return $this->userRepository->findOneBy(['email' => $email]);
    }

    public function findAll(): array
    {
        return $this->userRepository->findAll();
    }

    public function findActive(): array
    {
        return $this->userRepository->findBy(['active' => true], ['createdAt' => 'DESC']);
    }

    public function findPaginated(int $page, int $limit): array
    {
        return $this->userRepository->findBy(
            ['active' => true],
            ['createdAt' => 'DESC'],
            $limit,
            ($page - 1) * $limit
        );
    }
}

1.3 更新实体 #

php
<?php

class UserService
{
    public function update(User $user, array $data): User
    {
        if (isset($data['name'])) {
            $user->setName($data['name']);
        }

        if (isset($data['email'])) {
            $user->setEmail($data['email']);
        }

        if (isset($data['password'])) {
            $user->setPassword(password_hash($data['password'], PASSWORD_BCRYPT));
        }

        $this->entityManager->flush();

        return $user;
    }

    public function activate(User $user): void
    {
        $user->setActive(true);
        $this->entityManager->flush();
    }

    public function deactivate(User $user): void
    {
        $user->setActive(false);
        $this->entityManager->flush();
    }
}

1.4 删除实体 #

php
<?php

class UserService
{
    public function delete(User $user): void
    {
        $this->entityManager->remove($user);
        $this->entityManager->flush();
    }

    public function softDelete(User $user): void
    {
        $user->setDeletedAt(new \DateTimeImmutable());
        $this->entityManager->flush();
    }

    public function deleteById(int $id): bool
    {
        $user = $this->userRepository->find($id);
        
        if (!$user) {
            return false;
        }

        $this->entityManager->remove($user);
        $this->entityManager->flush();

        return true;
    }
}

二、生命周期回调 #

2.1 回调注解 #

php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\HasLifecycleCallbacks]
class User
{
    #[ORM\PrePersist]
    public function onPrePersist(): void
    {
        $this->createdAt = new \DateTimeImmutable();
        $this->updatedAt = new \DateTimeImmutable();
    }

    #[ORM\PostPersist]
    public function onPostPersist(): void
    {
        // 实体保存后的操作
    }

    #[ORM\PreUpdate]
    public function onPreUpdate(): void
    {
        $this->updatedAt = new \DateTimeImmutable();
    }

    #[ORM\PostUpdate]
    public function onPostUpdate(): void
    {
        // 实体更新后的操作
    }

    #[ORM\PreRemove]
    public function onPreRemove(): void
    {
        // 实体删除前的操作
    }

    #[ORM\PostRemove]
    public function onPostRemove(): void
    {
        // 实体删除后的操作
    }

    #[ORM\PostLoad]
    public function onPostLoad(): void
    {
        // 实体加载后的操作
    }
}

2.2 时间戳Trait #

php
<?php

namespace App\Entity\Traits;

use Doctrine\ORM\Mapping as ORM;

trait TimestampableTrait
{
    #[ORM\Column]
    private ?\DateTimeImmutable $createdAt = null;

    #[ORM\Column]
    private ?\DateTimeImmutable $updatedAt = null;

    public function getCreatedAt(): ?\DateTimeImmutable
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeImmutable $createdAt): static
    {
        $this->createdAt = $createdAt;
        return $this;
    }

    public function getUpdatedAt(): ?\DateTimeImmutable
    {
        return $this->updatedAt;
    }

    public function setUpdatedAt(\DateTimeImmutable $updatedAt): static
    {
        $this->updatedAt = $updatedAt;
        return $this;
    }

    #[ORM\PrePersist]
    public function setCreatedAtValue(): void
    {
        $this->createdAt = new \DateTimeImmutable();
        $this->updatedAt = new \DateTimeImmutable();
    }

    #[ORM\PreUpdate]
    public function setUpdatedAtValue(): void
    {
        $this->updatedAt = new \DateTimeImmutable();
    }
}

2.3 使用Trait #

php
<?php

namespace App\Entity;

use App\Entity\Traits\TimestampableTrait;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class Article
{
    use TimestampableTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: 'text')]
    private ?string $content = null;
}

三、软删除 #

3.1 软删除Trait #

php
<?php

namespace App\Entity\Traits;

use Doctrine\ORM\Mapping as ORM;

trait SoftDeletableTrait
{
    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $deletedAt = null;

    public function getDeletedAt(): ?\DateTimeImmutable
    {
        return $this->deletedAt;
    }

    public function setDeletedAt(?\DateTimeImmutable $deletedAt): static
    {
        $this->deletedAt = $deletedAt;
        return $this;
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt !== null;
    }

    public function softDelete(): void
    {
        $this->deletedAt = new \DateTimeImmutable();
    }

    public function restore(): void
    {
        $this->deletedAt = null;
    }
}

3.2 软删除过滤器 #

php
<?php

namespace App\Filter;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;

class SoftDeletableFilter extends SQLFilter
{
    public function addFilterConstraint(ClassMetadata $targetEntity, string $targetTableAlias): string
    {
        if (!$targetEntity->hasField('deletedAt')) {
            return '';
        }

        return $targetTableAlias . '.deleted_at IS NULL';
    }
}

3.3 配置过滤器 #

yaml
# config/packages/doctrine.yaml
doctrine:
    orm:
        filters:
            soft_deletable: App\Filter\SoftDeletableFilter

3.4 使用过滤器 #

php
<?php

use Doctrine\ORM\EntityManagerInterface;

class UserService
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {}

    public function findAllIncludingDeleted(): array
    {
        $filter = $this->entityManager->getFilters()->disable('soft_deletable');
        
        $users = $this->userRepository->findAll();
        
        $this->entityManager->getFilters()->enable('soft_deletable');
        
        return $users;
    }
}

四、实体验证 #

4.1 实体约束 #

php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 100)]
    #[Assert\NotBlank(message: '姓名不能为空')]
    #[Assert\Length(min: 2, max: 100, minMessage: '姓名至少2个字符')]
    private ?string $name = null;

    #[ORM\Column(length: 180, unique: true)]
    #[Assert\NotBlank(message: '邮箱不能为空')]
    #[Assert\Email(message: '邮箱格式不正确')]
    #[Assert\Length(max: 180)]
    private ?string $email = null;

    #[ORM\Column]
    #[Assert\NotBlank(message: '密码不能为空')]
    #[Assert\Length(min: 8, minMessage: '密码至少8个字符')]
    private ?string $password = null;

    #[ORM\Column]
    #[Assert\Range(min: 0, max: 150)]
    private int $age = 0;
}

4.2 验证实例 #

php
<?php

namespace App\Service;

use App\Entity\User;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class UserService
{
    public function __construct(
        private ValidatorInterface $validator
    ) {}

    public function validate(User $user): array
    {
        $errors = $this->validator->validate($user);
        
        $messages = [];
        foreach ($errors as $error) {
            $messages[$error->getPropertyPath()] = $error->getMessage();
        }
        
        return $messages;
    }

    public function isValid(User $user): bool
    {
        return count($this->validator->validate($user)) === 0;
    }
}

五、实体继承 #

5.1 单表继承 #

php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
abstract class Person
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    protected ?int $id = null;

    #[ORM\Column(length: 100)]
    protected ?string $name = null;
}

#[ORM\Entity]
class Employee extends Person
{
    #[ORM\Column(length: 100)]
    private ?string $department = null;

    #[ORM\Column]
    private float $salary = 0.0;
}

5.2 类表继承 #

php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['product' => Product::class, 'digital_product' => DigitalProduct::class])]
abstract class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    protected ?int $id = null;

    #[ORM\Column(length: 255)]
    protected ?string $name = null;

    #[ORM\Column]
    protected float $price = 0.0;
}

#[ORM\Entity]
#[ORM\Table(name: 'digital_products')]
class DigitalProduct extends Product
{
    #[ORM\Column(length: 255)]
    private ?string $downloadUrl = null;

    #[ORM\Column]
    private int $fileSize = 0;
}

六、实体事件监听器 #

6.1 创建监听器 #

php
<?php

namespace App\EventListener;

use App\Entity\User;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;

class UserListener
{
    public function prePersist(User $user, PrePersistEventArgs $event): void
    {
        $user->setCreatedAt(new \DateTimeImmutable());
        $user->setUpdatedAt(new \DateTimeImmutable());
        
        // 发送欢迎邮件
        $this->sendWelcomeEmail($user);
    }

    public function preUpdate(User $user, PreUpdateEventArgs $event): void
    {
        $user->setUpdatedAt(new \DateTimeImmutable());
        
        if ($event->hasChangedField('email')) {
            $this->handleEmailChange($user, $event->getOldValue('email'));
        }
    }

    private function sendWelcomeEmail(User $user): void
    {
        // 发送欢迎邮件逻辑
    }

    private function handleEmailChange(User $user, string $oldEmail): void
    {
        // 处理邮箱变更逻辑
    }
}

6.2 注册监听器 #

yaml
# config/services.yaml
services:
    App\EventListener\UserListener:
        tags:
            - { name: doctrine.orm.entity_listener, event: prePersist, entity: App\Entity\User }
            - { name: doctrine.orm.entity_listener, event: preUpdate, entity: App\Entity\User }

七、实体代理 #

7.1 理解代理 #

text
实体代理:
├── Doctrine使用代理类延迟加载关联
├── 代理类继承实体类
├── 首次访问时才查询数据库
└── 提高性能,减少不必要的查询

7.2 初始化代理 #

php
<?php

use Doctrine\Persistence\Proxy;

class UserService
{
    public function initializeProxy($entity): void
    {
        if ($entity instanceof Proxy) {
            $entity->__load();
        }
    }

    public function isProxyInitialized($entity): bool
    {
        if ($entity instanceof Proxy) {
            return $entity->__isInitialized();
        }
        
        return true;
    }
}

八、最佳实践 #

8.1 实体设计原则 #

text
实体设计原则:
├── 单一职责:每个实体代表一个概念
├── 使用值对象:复杂属性使用值对象
├── 避免贫血模型:实体包含业务逻辑
├── 使用Trait:复用通用属性和方法
├── 合理使用继承:避免过度继承
└── 软删除:重要数据使用软删除

8.2 性能优化 #

php
<?php

class UserService
{
    public function batchOperation(array $users): void
    {
        $batchSize = 100;
        
        foreach ($users as $i => $user) {
            $this->entityManager->persist($user);
            
            if (($i + 1) % $batchSize === 0) {
                $this->entityManager->flush();
                $this->entityManager->clear();
            }
        }
        
        $this->entityManager->flush();
    }

    public function updateWithDql(int $userId, string $newName): int
    {
        return $this->entityManager->createQueryBuilder()
            ->update(User::class, 'u')
            ->set('u.name', ':name')
            ->where('u.id = :id')
            ->setParameter('name', $newName)
            ->setParameter('id', $userId)
            ->getQuery()
            ->execute();
    }
}

九、总结 #

本章学习了:

  • 实体CRUD操作
  • 生命周期回调
  • 时间戳Trait
  • 软删除实现
  • 实体验证
  • 实体继承
  • 事件监听器
  • 实体代理

下一章将学习 查询构建器

最后更新:2026-03-28