Doctrine基础 #

一、Doctrine概述 #

1.1 什么是Doctrine #

Doctrine是Symfony默认的ORM(对象关系映射)框架,提供强大的数据库操作能力。

text
┌─────────────────────────────────────────────────────┐
│                  Doctrine 组成                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│  DBAL (数据库抽象层)                                │
│  ├── 数据库连接                                     │
│  ├── SQL查询构建                                    │
│  └── 平台抽象                                       │
│                                                     │
│  ORM (对象关系映射)                                 │
│  ├── 实体管理                                       │
│  ├── 查询构建器                                     │
│  ├── DQL查询语言                                    │
│  └── 关联映射                                       │
│                                                     │
└─────────────────────────────────────────────────────┘

1.2 ORM优势 #

优势 说明
面向对象 使用PHP对象操作数据库
数据库无关 轻松切换数据库类型
自动映射 自动转换数据类型
关联管理 自动处理表关联
缓存支持 提升查询性能

二、安装配置 #

2.1 安装Doctrine #

bash
# 安装Doctrine包
composer require doctrine

# 安装Maker Bundle(用于生成实体)
composer require --dev maker

2.2 数据库配置 #

bash
# .env 文件
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=8.0"

# PostgreSQL
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=14"

# SQLite
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"

2.3 Doctrine配置 #

yaml
# config/packages/doctrine.yaml
doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'
        driver: pdo_mysql
        server_version: '8.0'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

三、数据库操作 #

3.1 创建数据库 #

bash
# 创建数据库
php bin/console doctrine:database:create

# 删除数据库
php bin/console doctrine:database:drop --force

3.2 数据库连接测试 #

php
<?php

namespace App\Controller;

use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class DatabaseController extends AbstractController
{
    public function test(Connection $connection): Response
    {
        try {
            $connection->executeQuery('SELECT 1');
            return new Response('Database connected!');
        } catch (\Exception $e) {
            return new Response('Connection failed: ' . $e->getMessage());
        }
    }
}

3.3 原生SQL查询 #

php
<?php

use Doctrine\DBAL\Connection;

class ReportService
{
    public function __construct(
        private Connection $connection
    ) {}

    public function getUserStats(): array
    {
        $sql = 'SELECT COUNT(*) as total, status FROM users GROUP BY status';
        
        return $this->connection->fetchAllAssociative($sql);
    }

    public function searchUsers(string $keyword): array
    {
        $sql = 'SELECT * FROM users WHERE name LIKE :keyword';
        
        return $this->connection->fetchAllAssociative($sql, [
            'keyword' => '%' . $keyword . '%',
        ]);
    }

    public function updateUserStatus(int $id, string $status): int
    {
        return $this->connection->update('users', [
            'status' => $status,
            'updated_at' => new \DateTime(),
        ], ['id' => $id]);
    }
}

四、实体基础 #

4.1 创建实体 #

bash
# 创建实体
php bin/console make:entity User

# 输出
created: src/Entity/User.php
created: src/Repository/UserRepository.php

4.2 实体定义 #

php
<?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

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

    #[ORM\Column(length: 180, unique: true)]
    private ?string $email = null;

    #[ORM\Column]
    private bool $active = true;

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

    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 isActive(): bool
    {
        return $this->active;
    }

    public function setActive(bool $active): static
    {
        $this->active = $active;
        return $this;
    }

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

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

4.3 字段类型 #

php
<?php

use Doctrine\DBAL\Types\Types;

class Product
{
    #[ORM\Column]
    private ?int $id = null;

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

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    private ?string $description = null;

    #[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2)]
    private ?string $price = null;

    #[ORM\Column(type: Types::INTEGER)]
    private int $stock = 0;

    #[ORM\Column(type: Types::BOOLEAN)]
    private bool $active = true;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
    private ?\DateTimeImmutable $createdAt = null;

    #[ORM\Column(type: Types::JSON)]
    private array $metadata = [];

    #[ORM\Column(type: Types::SIMPLE_ARRAY)]
    private array $tags = [];

    #[ORM\Column(type: Types::DATE_IMMUTABLE)]
    private ?\DateTimeImmutable $publishedAt = null;

    #[ORM\Column(type: Types::TIME_IMMUTABLE)]
    private ?\DateTimeImmutable $openTime = null;
}

4.4 字段选项 #

php
<?php

class Article
{
    #[ORM\Column(
        name: 'article_title',
        length: 200,
        nullable: false,
        unique: true,
        options: [
            'comment' => '文章标题',
            'default' => '无标题'
        ]
    )]
    private ?string $title = null;

    #[ORM\Column(
        type: Types::STRING,
        length: 100,
        columnDefinition: 'VARCHAR(100) NOT NULL'
    )]
    private ?string $author = null;
}

五、迁移管理 #

5.1 创建迁移 #

bash
# 安装迁移包
composer require doctrine/migrations

# 创建迁移
php bin/console make:migration

# 查看迁移状态
php bin/console doctrine:migrations:status

# 执行迁移
php bin/console doctrine:migrations:migrate

# 回滚迁移
php bin/console doctrine:migrations:execute DoctrineMigrations\\Version20240101000000 --down

5.2 迁移文件 #

php
<?php

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20240101000000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Create users table';
    }

    public function up(Schema $schema): void
    {
        $this->addSql('
            CREATE TABLE users (
                id INT AUTO_INCREMENT NOT NULL,
                name VARCHAR(100) NOT NULL,
                email VARCHAR(180) NOT NULL,
                active TINYINT(1) NOT NULL,
                created_at DATETIME NOT NULL,
                UNIQUE INDEX UNIQ_1483A5E9E7927C74 (email),
                PRIMARY KEY(id)
            ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
        ');
    }

    public function down(Schema $schema): void
    {
        $this->addSql('DROP TABLE users');
    }
}

5.3 迁移配置 #

yaml
# config/packages/doctrine_migrations.yaml
doctrine_migrations:
    migrations_paths:
        DoctrineMigrations: '%kernel.project_dir%/migrations'
    enable_profiler: false

六、Repository #

6.1 创建Repository #

php
<?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 findActive(): array
    {
        return $this->createQueryBuilder('u')
            ->where('u.active = :active')
            ->setParameter('active', true)
            ->orderBy('u.createdAt', 'DESC')
            ->getQuery()
            ->getResult();
    }

    public function findByEmail(string $email): ?User
    {
        return $this->createQueryBuilder('u')
            ->where('u.email = :email')
            ->setParameter('email', $email)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function search(string $keyword): array
    {
        return $this->createQueryBuilder('u')
            ->where('u.name LIKE :keyword OR u.email LIKE :keyword')
            ->setParameter('keyword', '%' . $keyword . '%')
            ->getQuery()
            ->getResult();
    }
}

6.2 使用Repository #

php
<?php

namespace App\Controller;

use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class UserController extends AbstractController
{
    public function list(UserRepository $userRepository): Response
    {
        $users = $userRepository->findAll();
        $activeUsers = $userRepository->findActive();
        $user = $userRepository->find(1);
        $userByEmail = $userRepository->findByEmail('john@example.com');

        return $this->render('user/list.html.twig', [
            'users' => $users,
        ]);
    }
}

七、EntityManager #

7.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->setCreatedAt(new \DateTimeImmutable());

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

        return $user;
    }

    public function update(User $user, array $data): User
    {
        $user->setName($data['name'] ?? $user->getName());
        $user->setEmail($data['email'] ?? $user->getEmail());

        $this->entityManager->flush();

        return $user;
    }

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

7.2 批量操作 #

php
<?php

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

    public function batchInsert(array $users): void
    {
        $batchSize = 100;
        $i = 0;

        foreach ($users as $userData) {
            $user = new User();
            $user->setName($userData['name']);
            $user->setEmail($userData['email']);

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

            if (($i % $batchSize) === 0) {
                $this->entityManager->flush();
                $this->entityManager->clear();
            }
            $i++;
        }

        $this->entityManager->flush();
        $this->entityManager->clear();
    }

    public function batchUpdate(array $users): void
    {
        $batchSize = 100;
        $i = 0;

        foreach ($users as $user) {
            $user->setActive(false);

            if (($i % $batchSize) === 0) {
                $this->entityManager->flush();
                $this->entityManager->clear();
            }
            $i++;
        }

        $this->entityManager->flush();
    }
}

八、数据填充 #

8.1 创建Fixture #

bash
# 安装Fixture包
composer require --dev doctrine/fixtures-bundle
php
<?php

namespace App\DataFixtures;

use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class UserFixtures extends Fixture
{
    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->setCreatedAt(new \DateTimeImmutable());
            $user->setActive(true);

            $manager->persist($user);
            
            $this->addReference("user_$i", $user);
        }

        $manager->flush();
    }
}

8.2 关联Fixture #

php
<?php

namespace App\DataFixtures;

use App\Entity\Article;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;

class ArticleFixtures extends Fixture implements DependentFixtureInterface
{
    public function load(ObjectManager $manager): void
    {
        for ($i = 1; $i <= 20; $i++) {
            $article = new Article();
            $article->setTitle("Article $i");
            $article->setContent("Content for article $i");
            $article->setAuthor($this->getReference("user_" . rand(1, 10), User::class));

            $manager->persist($article);
        }

        $manager->flush();
    }

    public function getDependencies(): array
    {
        return [
            UserFixtures::class,
        ];
    }
}

8.3 执行Fixture #

bash
# 执行所有Fixture
php bin/console doctrine:fixtures:load

# 追加数据(不删除现有数据)
php bin/console doctrine:fixtures:load --append

九、总结 #

本章学习了:

  • Doctrine ORM概述
  • 数据库配置
  • 实体定义
  • 字段类型和选项
  • 迁移管理
  • Repository使用
  • EntityManager操作
  • 数据填充

下一章将学习 实体管理

最后更新:2026-03-28