数据库和Doctrine ORM

3.3 版本
维护中的版本

对于任何应用程序来说,一个最常见和最具挑战的任务,就是从数据库中读取和持久化数据信息。尽管symfony框架并未整合任何需要使用数据库的组件,但是却紧密集成了一个名为 Doctrine 的三方类库。Doctrine的主要目标是为你提供一个强有力的工具,令数据库互动更加轻松和灵活。

在本章,你将学习如何在Symfony项目中利用doctrine来提供丰富的数据库互动。

Note

Doctrine与symfony是完全解耦的,使用与否是可选的。本章讲的全部是Doctrine ORM,目的是让你把对象映射到关系型数据库中(如 MySQL, PostgreSQL 和 Microsoft SQL)。如果你倾向于使用数据库的原始查询,这很简单,可参考 如何使用Doctrine DBAL 一文的讲解。

你也可以使用Doctrine ODM类库将数据持久化到 MongoDB。参考 DoctrineMongoDBBundle 以了解更多信息。

简单例子:一件产品(Product) 

要了解Doctrine是如何工作的,最简单的方式就是看一个实际应用。在本节,你需要配置你的数据库,创建一个 Product 对象,把它持久化到数据库,再取回它。

配置数据库 

真正开始之前,你需要配置你的数据库连接信息。按照惯例,这部分信息通常配置在 app/config/parameters.yml 文件中:

1
2
3
4
5
6
7
8
# app/config/parameters.yml
parameters:
    database_host:      localhost
    database_name:      test_project
    database_user:      root
    database_password:  password
 
# ...

Note

通过 parameters.yml 来定义配置,只是一个惯例。配置Doctrine时,定义在那个文件中的参数,将被主配置文件引用:

1
2
3
4
5
6
7
8
# app/config/config.yml
doctrine:
    dbal:
        driver:   pdo_mysql
        host:     "%database_host%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
           xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/doctrine
        http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
 
    <doctrine:config>
        <doctrine:dbal
                driver="pdo_mysql"
                host="%database_host%"
                dbname="%database_name%"
                user="%database_user%"
                password="%database_password%" />
    </doctrine:config>
</container>
1
2
3
4
5
6
7
8
9
10
// app/config/config.php
$configuration->loadFromExtension('doctrine', array(
    'dbal' => array(
        'driver'   => 'pdo_mysql',
        'host'     => '%database_host%',
        'dbname'   => '%database_name%',
        'user'     => '%database_user%',
        'password' => '%database_password%',
    ),
));

通过把数据库信息分离到一个单独文件中,你可以很容易地为每个服务器保存不同的版本。你还可以在项目外轻松存储数据库配置(或任何敏感信息),举例来说,就和apache中的配置信息一样。参考 服务容器外部参数如何设置 以了解更多。

现在Doctrine可以连接你的数据库了,下面的命令可以自动生成一个空的 test_project 数据库:

1
$  php bin/console doctrine:database:create

设置数据库为UTF8

Symfony项目开始后,老练的程序员也会犯的一个错误是,忘了设置他们的数据库默认字符集和校对规则(charset and collation),最终变成latin类型,也就是多数数据库默认的。他们也许在第一次操作时会记得,但在后面的开发中敲打两行相关的常用命令之后就完全忘掉了:

1
2
$  php bin/console doctrine:database:drop --force
$  php bin/console doctrine:database:create

设置UTF8为MySQL的默认字符集简单到只要在配置文件(一般是my.cnf文件)中加几行代码就可以了:

1
2
3
4
[mysqld]
#Version 5.5.3 introduced "utf8mb4", which is recommended
collation-server     = utf8mb4_general_ci # Replaces utf8_general_ci
character-set-server = utf8mb4            # Replaces utf8

你还可以改变Doctrine的默认字符集,以便生成的SQL使用设置正确的字符集。

1
2
3
4
5
6
7
# app/config/config.yml
doctrine:
    dbal:
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
           xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/doctrine
        http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
 
    <doctrine:config>
        <doctrine:dbal
                charset="utf8mb4">
            <doctrine:default-table-option name="charset">utf8mb4</doctrine:default-table-option>
            <doctrine:default-table-option name="collate">utf8mb4_unicode_ci</doctrine:default-table-option>
        </doctrine:dbal>
    </doctrine:config>
</container>
1
2
3
4
5
6
7
8
9
10
// app/config/config.php
$configuration->loadFromExtension('doctrine', array(
    'dbal' => array(
        'charset' => 'utf8mb4',
        'default_table_options' => array(
            'charset' => 'utf8mb4'
            'collate' => 'utf8mb4_unicode_ci'
        )
    ),
));

我们推荐避免使用Mysql的 uft8 字符集,因为它并不兼容4-byte unicode字符,如果字符串中有这种字符会被清空。不过这种情况被修复了,参考 新型utf8mb4字符集

Note

如果你要用SQLite作为数据库,在path选项中设置你的数据库路径:

1
2
3
4
5
6
# app/config/config.yml
doctrine:
    dbal:
        driver: pdo_sqlite
        path: "%kernel.root_dir%/sqlite.db"
        charset: UTF8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
           xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/doctrine
        http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
 
    <doctrine:config>
        <doctrine:dbal
                driver="pdo_sqlite"
                path="%kernel.root_dir%/sqlite.db"
                charset="UTF-8" />
    </doctrine:config>
</container>
1
2
3
4
5
6
7
8
// app/config/config.php
$container->loadFromExtension('doctrine', array(
    'dbal' => array(
        'driver'  => 'pdo_sqlite',
        'path'    => '%kernel.root_dir%/sqlite.db',
        'charset' => 'UTF-8',
    ),
));

创建一个Entity类 

假设你正构建一套程序,其中有些产品需要展示。即使不考虑Doctrine或者数据库,你也已经知道你需要一个 Product 对象来呈现这些产品。在你AppBundle的 Entity 目录下创建这个类:

1
2
3
4
5
6
7
8
9
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
 
class Product
{
    private $name;
    private $price;
    private $description;
}

这个类——常被称作一个“Entity”,表示 一个保存着数据的基本类 ——它很简单,可以满足程序中所需产品的业务需求。这个类还不能被保存到数据库中——它只是个简单的PHP类。

Tip

一旦你学习了Doctrine背后的概念,你可以让Doctrine为你创建entity类。它将问你一些互动问题来帮你创建任意的entity:

1
$  php bin/console doctrine:generate:entity

添加映射信息 

Doctrine允许你以一种更加有趣的方式来使用数据库,而不只是把标量数据的行(rows)取出到数组中。Doctrine允许你从数据库中取出整个 对象,同时持久化整个对象到数据库中。对Doctrine来说要实现这些,你必须 映射 数据表到特定的PHP类中,那些表的列(columns)必须被映射为相应PHP类的特定属性。

doctrine_image_1

你要以“元数据(meatdata)”形式来提供这些映射信息,有一组规则可以准确告之Doctrine Product 类及其属性应该如何 映射到 一个特定的数据表。这个metadata可以通过不同的格式来指定,包括YAML,XML或者通过DocBlock注释(译注:annotations)直接定义到 Product 类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Entity
 * @ORM\Table(name="product")
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /**
     * @ORM\Column(type="string", length=100)
     */
    private $name;
 
    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    private $price;
 
    /**
     * @ORM\Column(type="text")
     */
    private $description;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# src/AppBundle/Resources/config/doctrine/Product.orm.yml
AppBundle\Entity\Product:
    type: entity
    table: product
    id:
        id:
            type: integer
            generator: { strategy: AUTO }
    fields:
        name:
            type: string
            length: 100
        price:
            type: decimal
            scale: 2
        description:
            type: text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- src/AppBundle/Resources/config/doctrine/Product.orm.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
        http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
 
    <entity name="AppBundle\Entity\Product" table="product">
        <id name="id" type="integer">
            <generator strategy="AUTO" />
        </id>
        <field name="name" type="string" length="100" />
        <field name="price" type="decimal" scale="2" />
        <field name="description" type="text" />
    </entity>
</doctrine-mapping>

Note

一个bundle只可以接受一种metadata的定义格式。比如,不能把YAML的metadata定义和添加了注释(annotation)的PHP entity类混用。

Tip

表名是可选的,如果省略,将自动取决于entity类的名称。

Doctrine允许你选择广泛的字段类型,每一种都有自己的配置。可用字段类型的信息,参考 Doctrine字段类型参考

Seealso

你也可以查看Doctrine官方文档 Basic Mapping Documentation 以了解关于映射的所有细节信息。如果你使用annotation,你需要为所有annotation加挂 ORM\ (例如 ORM\Column(...) ),这在Doctrine文档中并未写明。你还需要去包容 use Doctrine\ORM\Mapping as ORM; 声明,它可以 import(导入) ORM annotation前缀。

Caution

小心Entity类名(或者其属性)同时也是一个SQL保留的关键字(如 groupuser )。例如,如果你的entity类名称为 Group ,那么,默认时,你的表名将会是group,这在一些数据库引擎中可能导致SQL错误。参考 Reserved SQL keywords documentation 以了解如何正确规避这些名称。可选地,你可以任意选择数据库的schema,轻松映射成不同的表名或列名。参考 Creating Classes for the Database Property Mapping文档。

Note

当使用其他一些“使用了annotations”的类库或者程序(如Doxygen)时,你应该把 @IgnoreAnnotation 注释添加到类中,来指示Symfony应该忽略哪个annotation。

例如,要避免 @fn annotation抛出异常,添加下列注释:

1
2
3
4
5
/**
 * @IgnoreAnnotation("fn")
 */
class Product
// ...

Tip

创建entity之后,你应该使用以下命令来验证映射(mappings):

1
$  php bin/console doctrine:schema:validate

生成Getters和Setters 

尽管Doctrine现在知道了如何持久化 Product 对象到数据库,但是类本身还不具备真正用途。因为 Product 仅仅是一个带有 private 属性的常规PHP类,你需要创建 public 的getter和setter方法(比如 getName() , setName($name) )以便在程序其他部分来访问它的属性(其属性是protected)。幸运的是,下面的命令可以自动生成这些模板化的方法:

1
$  php bin/console doctrine:generate:entities AppBundle/Entity/Product

该命令可以确保 Product 类所有的getter和setter都被生成。这是一个安全的命令行——你可以多次运行它,它只会生成那些不存在的getters和setters(即,不会替换已有的方法)。

Caution

重要提示 下面这句话极其深刻,乃是活用Doctrine的关键。大家一定照做。

记得,doctrine entity generator生成的是简单的getters/setters。你应该复审那些已生成的方法,在必要时,添加逻辑进去,以满足你的程序之需求。

关于 doctrine:generate:entities 的更多内容

使用 doctrine:generate:entities 命令你可以:

  • 在entity类中生成getters和setters;

  • 在entity类配置了 @ORM\Entity(repositoryClass=”…”) annotation的情况下生成所对应的repository类;

  • 为 1:n 或 n:m 生成合适的构造器。

doctrine:generate:entities 命令会保存原始 Product.php 文件的备份并命名为 Product.php~ 。 有些时候这个文件可能会引发“Cannot redeclare class”错误。它可以被安全删除。你还可以使用 –no-backup 选项,来防止生成这些备份文件。

注意,你并不 需要 (依赖于)此命令。你也可以手写getters和setters。这个选项的存在只是为了节省你的时间,因为在开发过程中创建这些方法是一个常见任务。

你也可以为一个bundle或者一个entity命名空间内的所有已知实体(任何包含Doctrine映射信息的PHP类)来生成getter和setter:

1
2
3
4
5
6
7
# generates all entities in the AppBundle
# 生成AppBundle下的全部entities
$  php bin/console doctrine:generate:entities AppBundle
 
# generates all entities of bundles in the Acme namespace
# 生成Acme命名空间下的bundles的全部entities
$  php bin/console doctrine:generate:entities Acme

创建数据表/Schema 

现在你有了一个包含映射信息的可用 Product 类,因此Doctrine确切地知道如何持久化它。当然,你还没有相应的 product 数据表在库中。幸运的是,Doctrine可以自动创建所有的数据表。要这么做,运行以下命令:

1
$  php bin/console doctrine:schema:update --force

Tip

说真的,这条命令出奇的强大。它会比较你的数据库 理论上应该是 什么样子的(基于你的entities的映射信息)以及 实际上 它应该是什么样,然后执行所需的SQl语句来将数据库的schema 更新到 它所应有的样子。换句话说,如果你添加了一个包含“映射元数据”(mapping metadata)的新属性到 Product 并运行此任务,它将执行所需的 "ALTER TABLE" 语句,向已经存在的 product 表添加那个新列。

一个利用此功能之优势的更佳方式是通过 migrations,它允许你生成这些SQL语句,并把它们并存储到migration类中,这些类能够有序运行在你的生产环境中,进而安全可靠地更新和追踪数据库的schema改变。

不管你是否利用了数据库迁移,doctrine:schema:update 命令只适合在开发环境中使用。它不应该被用于生产环境。

现在你的数据库中有了一个全功能的product表,它的列与你指定的元数据相匹配。

持久化对象到数据库 

现在你有了一个Product实体和与之映射的product数据库表。你可以把数据持久化到数据库里。在Controller内,它非常简单。添加下面的方法到bundle的DefaultController中。

现在你已经把 Product entity 映射到与之对应的 product 表中,你已经准备好把 Product 对象持久化到数据库中。在控制器里面,这极其简单。向bundle的 DefaultController 添加以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/AppBundle/Controller/DefaultController.php
 
// ...
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
 
// ...
public function createAction()
{
    $product = new Product();
    $product->setName('Keyboard');
    $product->setPrice(19.99);
    $product->setDescription('Ergonomic and stylish!');
 
    $em = $this->getDoctrine()->getManager();
 
    // tells Doctrine you want to (eventually) save the Product (no queries yet)
    // 告诉Doctrine你希望(最终)存储Product对象(还没有语句执行)
    $em->persist($product);
 
    // actually executes the queries (i.e. the INSERT query)
    // 真正执行语句(如,INSERT 查询)
    $em->flush();
 
    return new Response('Saved new product with id '.$product->getId());
}

Note

如果你正在跟进本例程,需要创建一个路由,并指向这个action,才能看到它运行。

Tip

本例展示了在控制器中使用Doctrine的 getDoctrine() 方法。这是取出 doctrine 服务的快捷方法。若你在服务中注入此服务,即可在任意地方使用doctrine。参考 服务容器 以了解更多创建服务之内容。

深入分析一下前面的例子:

  • 10-13行 在此处实例化,并且像其他常规PHP对象一样去使用 $product 对象。
  • 15行 这一行取出了Doctrine的 entity manager 对象,它负责处理数据库的持久化(译注:写入)和取出对象的过程。
  • 18行 persist($product) 调用,告诉Doctrine去 "管理" $product 对象。它 没有 引发对数据库的请求。
  • 21行flush() 方法被调用时,Doctrine会遍历它管理的所有对象以确定是否需要被持久化到数据库。本例中, $product 对象的数据在库中并不存在,因此entity manager要执行 INSERT 请求,在 product 表中创建一个新行。

Note

事实上,由于Doctrine了解你的全部被管理的实体,当你调用 flush() 方法时,它会计算出所有的变更集合(changeset),并按正确顺序执行语句。它利用准备好的缓存语句以略微提高性能。比如,你要持久化总数为100的 Product 对象,然后调用 flush() 方法,Doctrine将用一个单一的prepare语法对象,来执行100次 INSERT 请求。

Note

如果 flush() 调用失败,一个 Doctrine\ORM\ORMException 异常会被抛出。参考 Transactions and Concurrency(处理和并发)。

在创建和更新对象时,工作流是相同的。在下一小节你将看到,如果记录已经存在于数据库中,Doctrine是如何聪明地自动发出一个 Update 语句的。

Tip

Doctrine提供了一个类库,允许你程序化地加载测试数据到你的项目中(即,"fixture data",固定的数据)。参考 DoctrineFixturesBundle 以了解更多。

从数据库中获取对象 

从数据库中取回对象就更简单了,举个例子,假如你配置了一个路由,基于产品的 id 来显示特定的 Product 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function showAction($productId)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->find($productId);
 
    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$productId
        );
    }
 
    // ... do something, like pass the $product object into a template
    // ... 做一些事,比如把 $product 对象传入模板
}

Tip

你可以使用 @ParamConverter 快捷注释,毋须编写任何代码即可实现同样的功能。参考 FrameworkExtraBundle 以了解更多。

当你要查询某个特定类型的对象时,你总是要使用它的”respository”(宝库)。你可以认为Respository是一个PHP类,它的唯一工作就是帮助你从那个特定的类中取出entity。对于一个entity类,要访问其宝库,通过:

1
2
$repository = $this->getDoctrine()
    ->getRepository('AppBundle:Product');

Note

appBundle:Product 是快捷写法,你可以在Doctrine里随处使用,以替代entity类的FQCN类名(如 AppBundle\Entity\Product )。只要你的entity存放在bundle的 Entity 命名空间下,它就会工作。

一旦有了Repository对象,你就可以访问它的全部有用的方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
 
// query for a single product by its primary key (usually "id")
// 通过主键(通常是id)查询一件产品
$product = $repository->find($productId);
 
// dynamic method names to find a single product based on a column value
// 动态方法名称,基于字段的值来找到一件产品
$product = $repository->findOneById($productId);
$product = $repository->findOneByName('Keyboard');
 
// dynamic method names to find a group of products based on a column value
// 动态方法名称,基于字段值来找出一组产品
$products = $repository->findByPrice(19.99);
 
// find *all* products / 查出 *全部* 产品
$products = $repository->findAll();

Note

当然,你也可以使用复杂的查询,参考 对象查询 小节 。

你也可以有效利用 findByfindOneBy 方法,基于多个条件来轻松获取对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
 
// query for a single product matching the given name and price
// 查询一件产品,要匹配给定的名称和价格
$product = $repository->findOneBy(
    array('name' => 'Keyboard', 'price' => 19.99)
);
 
// query for multiple products matching the given name, ordered by price
// 查询多件产品,要匹配给定的名称和价格
$products = $repository->findBy(
    array('name' => 'Keyboard'),
    array('price' => 'ASC')
);

Tip

渲染任何页面时,你可以在除错工具条(web debug toolbar)的右下角看到许多查询。

doctrine_web_debug_toolbar

如果你点击图标,分析器(profiler)将会打开,显示出所产生的精确查询。

如果你的页面查询超过了50个,图标会变成黄色。这表明某些地方不大对劲。

对象更新 

一旦从Doctrine中获取了一个对象,更新它就很容易了。假设你有一个路由,把一个产品id映射到controller的updateaction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function updateAction($productId)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('AppBundle:Product')->find($productId);
 
    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$productId
        );
    }
 
    $product->setName('New product name!');
    $em->flush();
 
    return $this->redirectToRoute('homepage');
}

更新一个对象包括三步:

  1. 从Doctrine中取出对象;
  2. 修改对象;
  3. 调用entity manager的 flush() 方法。

注意调用 $em->persist($product) 是不必要的。回想一下,这个方法只是告诉Doctrine去管理或者“观察” $product 对象。此处,因为你已经取到了 $product 对象了,它已经被管理了。

删除对象 

删除一个对象十分类似,但需要从entity manager调用 remove() 方法:

1
2
$em->remove($product);
$em->flush();

你可能已经预期,remove() 方法通知Doctrine你想从数据库中删除指定的entity。真正的 DELETE 查询不会被真正执行,直到 flush() 方法被调用。

对象查询 

你已经看到repository对象是如何让你执行一些基本查询而毋须做任何工作了:

1
2
3
4
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
 
$product = $repository->find($productId);
$product = $repository->findOneByName('Keyboard');

当然,Doctrine 也允许你使用Doctrine Query Language(DQL)来写一些复杂的查询,DQL类似于SQL,只是它用于查询一个或者多个entity类的对象(如 product),而SQL则是查询一个数据表中的行(如 product )。

在Doctrine中查询时,你有两个主要选择:编写纯正的Doctrine查询(DQL) 或者 使用Doctrine的Query Builder。

使用DQL进行对象查询 

假设你要查询价格高于 19.99 的产品,并且按价格从低到高排列。你可以使用DQL,Doctrine中类似原生SQL的语法,来构造一个用于此场景的查询:

1
2
3
4
5
6
7
8
9
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
    'SELECT p
    FROM AppBundle:Product p
    WHERE p.price > :price
    ORDER BY p.price ASC'
)->setParameter('price', 19.99);
 
$products = $query->getResult();

如果你习惯了写SQL,那么对于DQL也会非常自然。它们之间最大的不同就是你需要就“select PHP对象”来进行思考,而不是数据表的行。正因为如此,你要 AppBundle:Product 这个 entity (可选的一个AppBundle\Entity\Product 类的快捷写法)来select,然后给entity一个 p 的别名。

Tip

注意 setParameter() 方法。当使用Doctrine时,通过“占位符”来设置任意的外部值(上面例子的 :price),是一个好办法,因为它可以防止SQL注入攻击。

getResult() 方法返回一个结果数组。要得到一个结果,可以使用getSingleResult()(这个方法在没有结果时会抛出一个异常)或者 getOneOrNullResult()

1
$product = $query->setMaxResults(1)->getOneOrNullResult();

DQL语法强大到令人难以置信,允许轻松地在entity之间进行join(稍后会覆盖relations)和group等。参考 Doctrine Query Language 文档以了解更多。

使用Doctrine's Query Builder进行对象查询 

不去写DQL的大字符串,你可以使用一个非常有用的QueryBuilder对象,来构建那个字符串。当你的查询取决于动态条件时,这很有用,因为随着你的连接字符串不断增加,DQL代码会越来越难以阅读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$repository = $this->getDoctrine()
    ->getRepository('AppBundle:Product');
 
// createQueryBuilder() automatically selects FROM AppBundle:Product
// and aliases it to "p"
// createQueryBuilder() 自动从 AppBundle:Product 进行 select 并赋予 p 假名
$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();
 
$products = $query->getResult();
// to get just one result: / 要得到一个结果:
// $product = $query->setMaxResults(1)->getOneOrNullResult();

QueryBuilder对象包含了创建查询时的所有必要方法。通过调用getQuery()方法,query builder将返回一个标准的Query对象,可用于取得请求的结果集。

Query Builder更多信息,参考Doctrine的 Query Builder 文档。

把自定义查询组织到Repository类中 

前面所有的查询是直接写在你的控制器中的。但对于程序的组织来说,Doctrine提供了一个专门的repository类,它允许你保存所有查询逻辑到一个中心位置。

参考 如何创建自定义Repository类 以了解更多。

配置 

Doctrine是高度可配置的,虽然你可能永远不会去关心那些选项。要了解Doctrine的配置信息,参考 config reference

Doctrine字段类型参考 

Doctrine配备了大量可用的字段类型。每一个都能把PHP数据类型映射到特定的字段类型中,无论你使用什么数据库。对于每一个字段类型, Column 都可以被进一步配置,可以设置 lengthnullable 行为,name 或者其他选项。可用字段类型的列表,参考 Mapping Types documentation

Associations(关系) 和 Relations(关联) 

Doctrine 提供了你所需要的管理数据库关系(也被称为关联-associations)的所有的功能。更多信息,参考 如何使用Doctrine Associations / Relations

总结 

有了Doctrine,你可以集中精力到你的 对象 以及 如何把它应用到程序中,而数据库持久化则是第二位。这是因为Doctrine允许你使用任何的PHP对象来保存你的数据,并且依靠“元数据映射”信息来把一个对象的数据映射到一个特定的数据表之中。

Doctrine有很多强大的功能等着你去学习,像是relationships(关联),复杂查询和事件监听。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)