Doctrine是一个基于PHP的对象关系映射(ORM),它构建在强大的数据库抽象层(DBAL)之上,透明地为PHP对象提供持久化。

你可以从官方文档中读到更多关于Doctrine ORM 的内容。

首先,先激活和配置Doctrine DBAL,然后激活ORM。如果你遵循本章的说明和约定,你只需要告诉Doctrine自动映射你的实体:

   
   
   
   
  1. # app/config/config.yml 
  2. doctrine: 
  3.     orm: 
  4.         auto_mapping: true 

因为Doctrine透明地提供PHP对象的持久化,所以它可以与任何PHP类一起工作:

   
   
   
   
  1. // src/Acme/HelloBundle/Entity/User.php 
  2. namespace Acme\HelloBundle\Entity; 
  3.  
  4. class User 
  5.     protected $id
  6.     protected $name
  7.  
  8.     public function getId() 
  9.     { 
  10.         return $this->id; 
  11.     } 
  12.  
  13.     public function setName($name
  14.     { 
  15.         $this->name = $name
  16.     } 
  17.  
  18.     public function getName() 
  19.     { 
  20.         return $this->name; 
  21.     } 
  22. }

当你的实体被定义之后,当你使用doctrine:generate:entities命令时,你可以忽略getter/setter方法而让Doctrine为你创建它们。这只在你创建了映射信息之后才能正常工作(参见下面)

为了让Doctrine管理你的类(Doctrine2中叫实体),你需要使用注释、XML或YAML来编写映射信息:

   
   
   
   
  1. // src/Acme/HelloBundle/Entity/User.php 
  2. namespace Acme\HelloBundle\Entity; 
  3.  
  4. use Doctrine\ORM\Mapping as ORM; 
  5.  
  6. /** 
  7.  * @ORM\Entity 
  8.  */ 
  9. class User 
  10.     /** 
  11.      * @ORM\Id 
  12.      * @ORM\Column(type="integer") 
  13.      * @ORM\GeneratedValue(strategy="AUTO") 
  14.      */ 
  15.     protected $id
  16.  
  17.     /** 
  18.      * @ORM\Column(type="string", length="255") 
  19.      */ 
  20.     protected $name

当你在Symfony2项目中使用注释时,你不得不将所有Doctrine ORM的名字空间注释都使用rom:前缀。

如果你使用YAML或XML去描述你的实体时,你可以忽略实体类的创建,并让doctrine:generate:entities命令来为你自动生成。

为了不用为每个实体创建一个文件,你也可以在doctrine.orm.yml文件中定义你的映射信息。

用下列命令创建与你元数据信息相关的数据库和模式

   
   
   
   
  1. $ php app/console doctrine:database:create 
  2. $ php app/console doctrine:schema:create 

最后,使用你的实体,并且用Doctrine来管理它的持久化状态:

   
   
   
   
  1. // src/Acme/HelloBundle/Controller/UserController.php 
  2. namespace Acme\HelloBundle\Controller; 
  3.  
  4. use Acme\HelloBundle\Entity\User; 
  5.  
  6. class UserController extends Controller 
  7.     public function createAction() 
  8.     { 
  9.         $user = new User(); 
  10.         $user->setName('Jonathan H. Wage'); 
  11.  
  12.         $em = $this->get('doctrine')->getEntityManager(); 
  13.         $em->persist($user); 
  14.         $em->flush(); 
  15.  
  16.         // ... 
  17.     } 
  18.  
  19.     public function editAction($id) 
  20.     { 
  21.         $em = $this->get('doctrine')->getEntityManager(); 
  22.         $user = $em->find('AcmeHelloBundle:User', $id); 
  23.         $user->setBody('new body'); 
  24.         $em->persist($user); 
  25.         $em->flush(); 
  26.  
  27.         // ... 
  28.     } 
  29.  
  30.     public function deleteAction($id) 
  31.     { 
  32.         $em = $this->get('doctrine')->getEntityManager(); 
  33.         $user = $em->find('AcmeHelloBundle:User', $id); 
  34.         $em->remove($user); 
  35.         $em->flush(); 
  36.  
  37.         // ... 
  38.     } 

现在问题出现了,如果你想改变你的映射信息并更新你的开发数据库模式,而不想丢掉设置并失去已有数据的话,那么首先我们应该在我们的User实体中添加一个new属性:

   
   
   
   
  1. // src/Acme/HelloBundle/Entity/User.php 
  2. namespace Acme\HelloBundle\Entity; 
  3.  
  4. use Doctrine\ORM\Mapping as ORM; 
  5.  
  6. /** @ORM\Entity */ 
  7. class User 
  8.     /** @ORM\Column(type="string") */ 
  9.     protected $new
  10.  
  11.     // ... 

当你需要那样做,将你的数据库模式更新到new字段时,你只需要去运行下列命令:

   
   
   
   
  1. $ php app/console doctrine:schema:update 

现在你的数据库被更新且new字段被添加到数据库的数据表中。

查询

如你所见,使用实体管理器操作一个对象是简单容易的。但你如何查询对象集呢?如同所有Doctrine操作一样,这也是可以通过实体管理器做到的。在上个例子中可以使用查询语句来改变删除先引导对象再将其删除的删除方法:

   
   
   
   
  1. public function deleteAction($id
  2.     $query = $this->get('doctrine')->getEntityManager() 
  3.                ->createQuery('DELETE FROM Acme\HelloBundle\Entity\User u 
  4.                         WHERE u.id = :id'); 
  5.     $query->setParameters(array
  6.                 'id' => $id 
  7.     )); 
  8.  
  9.     $query->execute(); 
  10.  
  11.     // ... 

当然,你也可以使用SELECT和UPDATE查询。Doctrine带来了它自己的查询语言DQL(Doctrine Query Language)。DQL跟SQL有些相似,但查询语言有它自己的语法。

你可以在官方网站查看更多关于Doctrine查询语言 。DQL的优点在于它是数据库未知的,也就是说使用DQL所写的同一查询可以在任何支持的数据库引擎上工作。

使用DQL的DELETE或UPDATE语句是有缺陷的。具体来说,因为是直接对数据库操作,Doctrine并不知道内部细节。举个例子,如果你对一个对象查询,然后通过UPDATE语句直接在数据库中对该对象进行更新,该对象本身不会反映这个更新。因此,小心使用这些语句,在单个请求里,你的对象可能会和你数据库不同步。

Repositories

在Symfony2的控制器中使用查询是很不好的做法,查询应该在你Bundle的模型层中使用,以便能够测试它们并在你的应用程序中重用。幸运地是,Doctrine允许你使用名为Repositories的特殊类来封装查询。

Doctrine为你的repository类提供了一个缺省实现,因此你可以使用它们的通用方法去查询你的实体数据。其中之一就是findAll方法。

   
   
   
   
  1. $em = $this->get('doctrine')->getEntityManager(); 
  2. $users = $em->getRepository('AcmeHelloBundle:User')->findAll(); 

如果你想创建你自己的函数去查询和维护你的数据,你需要为一个实体创建一个自定义的Repository类。要做到这一点,你需要在你的映射定义中添加repository类的名称。

   
   
   
   
  1. // src/Acme/HelloBundle/Entity/User.php 
  2. namespace Acme\HelloBundle\Entity; 
  3.  
  4. use Doctrine\ORM\Mapping as ORM; 
  5.  
  6. /** 
  7.  * @ORM\Entity(repositoryClass="Acme\HelloBundle\Repository\UserRepository"
  8.  */ 
  9. class User 
  10.     //... 

如果你运行以下命令生成你的实体,那么repository类会自动创建。

   
   
   
   
  1. $ php app/console doctrine:generate:entities 

如果你在添加repositoryClass映射之前就已经生成了你的实体,那么你就不得不自行创建这个类了。幸运地是,它非常容易,简单地在你Bundle目录下的Repository/目录创建一个类,并确保它继承Doctrine\ORM\EntityRepository类。一旦你创建了这个类,你就可以为你的实体添加任意方法了。

下列代码示范了一个repository类的示例。

   
   
   
   
  1. // src/Acme/HelloBundle/Repository/UserRepository.php 
  2. namespace Acme\HelloBundle\Repository; 
  3.  
  4. use Doctrine\ORM\EntityRepository; 
  5.  
  6. class UserRepository extends EntityRepository 
  7.     public function findAllOrderedByName() 
  8.     { 
  9.         return $this->getEntityManager() 
  10.                     ->createQuery('SELECT u FROM Acme\HelloBundle\Entity\User u 
  11.                                     ORDER BY u.name ASC') 
  12.                     ->getResult(); 
  13.     } 

实体管理器在repositories函数中可能通过$this->getEntityManager()来访问。

新方法的用法和缺省查找函数一样。

   
   
   
   
  1. $em = $this->get('doctrine')->getEntityManager(); 
  2. $users = $em->getRepository('AcmeHelloBundle:User'
  3.             ->findAllOrderedByName(); 

配置

我们在Symfony2中只是知道了让Doctrine ORM运行的必要配置选项,其它的配置选项都使用缺省值。

下面的配置示例显示了ORM的所有缺省配置:

   
   
   
   
  1. doctrine: 
  2.     orm: 
  3.         auto_mapping: true 
  4.         auto_generate_proxy_classes: true 
  5.         proxy_namespace: Proxies 
  6.         proxy_dir: %kernel.cache_dir%/doctrine/orm/Proxies 
  7.         default_entity_manager: default 
  8.         metadata_cache_driver: array 
  9.         query_cache_driver: array 
  10.         result_cache_driver: array 

这有许多其它的配置选项,你可以用来覆写某些类,但那些仅用于非常高级的用例。你可以看看配置指南 ,以便对所有支持的选项有个了解。

你可以指定缓存驱动的值,象"array"、"apc"、"memcache"或"xcache"。

下面的例子展示了缓存配置的概况:

   
   
   
   
  1. doctrine: 
  2.     orm: 
  3.         auto_mapping: true 
  4.         metadata_cache_driver: apc 
  5.         query_cache_driver: xcache 
  6.         result_cache_driver: 
  7.             type: memcache 
  8.             host: localhost 
  9.             port: 11211 
  10.             instance_class: Memcache 

映射配置

显式定义所有被映射的实体是ORM唯一必要的配置,你可以控制一些配置选项,下面是一些映射配置选项:

  • type :在注释、xml、yml、php或staticphp中,它指定你映射所用的元数据类型;
  • dir :指向映射或实体文件路径(依赖驱动),如果这是个相对路径,那么它假定Bundle的根目录是它的根。它只在你映射的名称是Bundle名时工作。如果你想使用这个选项指向一个绝对路径,那么你应该使用在DIC中的内核参数来做它的前缀(如%kernel.root_dir%);
  • prifix :在这个映射的所有实体中共享的通用名称空间前缀这个前缀应该永远不能与其它定义的映射前缀冲突,否则你的一些实体将不能被Doctrine发现。这个选项缺省指向Bundle名称空间+Entity。如应用程序Bundle叫AcmeHelloBundle,那么prefixy就应该是Acme\HelloBundle\Entity
  • alias :Doctrine为实体的名称空间提供了别名方式(使用简短的名称),可以在DQL查询或Repository访问中使用。当使用一个Bundle时别名会默认为该Bundle的名称。
  • is_bundle :该选项是从dir派生出来的值,缺省为真。当dir使用相对路径,用file_exists()检查验证返回false时该值为真,反之为假。

为了避免为你的映射配置大量信息,你应该遵循以下约定:

  • 将你所有的实体放置在Bundle目录下的Entity/目录。如Acme/HelloBundle/Entity/;
  • 如果你使用xml、yml或php映射,那么将你所有的配置文件都放在Resources/config/doctrine/目录中,后缀分别是orm.xml、orm.yml或orm.php;
  • 如果只有一个Entity/目录,而Resources/config/doctrine/目录未找到时假定使用注释方式

以下配置展示了多个映射的例子:

   
   
   
   
  1. doctrine: 
  2.     orm: 
  3.         auto_mapping: false 
  4.         mappings: 
  5.             MyBundle1: ~ 
  6.             MyBundle2: yml 
  7.             MyBundle3: { type: annotation, dir: Entity/ } 
  8.             MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping } 
  9.             MyBundle5: 
  10.                 type: yml 
  11.                 dir: my-bundle-mappings-dir 
  12.                 alias: BundleAlias 
  13.             doctrine_extensions: 
  14.                 type: xml 
  15.                 dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entity 
  16.                 prefix: DoctrineExtensions\Entity\ 
  17.                 alias: DExt 

多实体管理

你可以在Symfony2应用程序中使用多实体管理。如果你使用不同的数据库甚至供应商使用完全不同的实体集。

下列配置代码展示了如何定义两个实体管理器

   
   
   
   
  1. doctrine: 
  2.     orm: 
  3.         default_entity_manager:   default 
  4.         cache_driver:             apc           # array, apc, memcache, xcache 
  5.         entity_managers: 
  6.             default
  7.                 connection:       default 
  8.                 mappings: 
  9.                     MyBundle1: ~ 
  10.                     MyBundle2: ~ 
  11.             customer: 
  12.                 connection:       customer 
  13.                 mappings: 
  14.                     MyBundle3: ~ 

与DBAL一样,如果你已经配置了多个实体管理器实例,并且想得到指定的那个,你可以从Doctrine注册中用它的名称检索到它:

   
   
   
   
  1. class UserController extends Controller 
  2.     public function indexAction() 
  3.     { 
  4.         $em =  $this->get('doctrine')->getEntityManager(); 
  5.         $customerEm =  $this->get('doctrine')->getEntityManager('customer'); 
  6.     } 

注册事件监听和订阅

Doctrine 使用轻量级的Doctrine\Common\EventManager类来跟踪你想要“钩进”的大量不同的事件。你可以通过doctrine.event_listener或doctrine.event_subscriber使用服务容器来标识各自的服务,从而注册事件监听器或订阅。

要注册事件监听器或订阅(监听器来源于此)服务,你必须要使用适当的名称来标识它们。依赖你的用户,你可以将监听器“钩进”每个DBAL连接和ORM实体管理器中,或者只是一个指定的DBAL连接和使用该连接的所有实体管理器。

   
   
   
   
  1. doctrine: 
  2.     dbal: 
  3.         default_connection: default 
  4.         connections: 
  5.             default
  6.                 driver: pdo_sqlite 
  7.                 memory: true 
  8.  
  9. services: 
  10.     my.listener: 
  11.         class: MyEventListener 
  12.         tags: 
  13.             - { name: doctrine.event_listener } 
  14.     my.listener2: 
  15.         class: MyEventListener2 
  16.         tags: 
  17.             - { name: doctrine.event_listener, connection: default } 
  18.     my.subscriber: 
  19.         class: MyEventSubscriber 
  20.         tags: 
  21.             - { name: doctrine.event_subscriber, connection: default }

注册自定义的DQL函数

你可以通过配置注册自定义的DQL函数。

   
   
   
   
  1. # app/config/config.yml 
  2. doctrine: 
  3.     orm: 
  4.         # ... 
  5.         entity_managers: 
  6.             default: 
  7.                 # ... 
  8.                 dql: 
  9.                     string_functions: 
  10.                         test_string: Acme\HelloBundle\DQL\StringFunction 
  11.                         second_string: Acme\HelloBundle\DQL\SecondStringFunction 
  12.                     numeric_functions: 
  13.                         test_numeric: Acme\HelloBundle\DQL\NumericFunction 
  14.                     datetime_functions: 
  15.                         test_datetime: Acme\HelloBundle\DQL\DatetimeFunction 

控制行命令

Doctrine2 ORM 在doctrine名称空间下整合提供了一些控制行命令,为了看到你可以在控制行运行的命令列表,不要加任何参数或选项:

   
   
   
   
  1. $ php app/console 
  2. ... 
  3.  
  4. doctrine 
  5.   :ensure-production-settings  Verify that Doctrine is properly configured for a production environment. 
  6.   :schema-tool                 Processes the schema and either apply it directly on EntityManager or generate the SQL output
  7. doctrine:cache 
  8.   :clear-metadata              Clear all metadata cache for a entity manager. 
  9.   :clear-query                 Clear all query cache for a entity manager. 
  10.   :clear-result                Clear result cache for a entity manager. 
  11. doctrine:fixtures 
  12.   :load                        Load data fixtures to your database
  13. doctrine:database 
  14.   :create                      Create the configured databases. 
  15.   :drop                        Drop the configured databases. 
  16. doctrine:generate 
  17.   :entities                    Generate entity classes and method stubs from your mapping information. 
  18.   :entity                      Generate a new Doctrine entity inside a bundle. 
  19.   :proxies                     Generates proxy classes for  
  20.  
  21. entity classes. 
  22.   :repositories                Generate repository classes from your mapping information. 
  23. doctrine:mapping 
  24.   :convert                     Convert mapping information between supported formats. 
  25.   :convert-d1-schema           Convert a Doctrine1 schema to Doctrine2 mapping files. 
  26.   :import                      Import mapping information from an existing database
  27. doctrine:query 
  28.   :dql                         Executes arbitrary DQL directly from the command line. 
  29.   :sql                         Executes arbitrary SQL directly from the command line. 
  30. doctrine:schema 
  31.   :create                      Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output
  32.   :drop                        Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output
  33.   :update                      Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output
  34.  
  35. ... 

为了能引导数据夹到你的数据库中,你需要安装DoctrineFixturesBundle,如何安装请参见食谱条目如何在Symfony2中创建数据夹

表单集成

在Doctrine ORM和Symfony2 Form组件之间的集成是紧密的。因为Doctrine实体是POPO(plain old php objects),在缺省情况下可以很好地集成到Form组件中,至少原始的数据类型如字符串、整数和字段没有问题。你也可以使用associations来很好地集成它们。

使用专用类型EntityType是有帮助的,它提供了来自被选实体的选择列表:

   
   
   
   
  1. use Symfony\Bridge\Doctrine\Form\Type\EntityType; 
  2.  
  3. $builder->add('users','entity'
  4.      array('class' => 'Acme\\HelloBundle\\Entity\\User'
  5. )); 

必填的class选项需要一个实体类名作为参数,可选的property选项允许你选择用于展示的实体属性(如果没有设置则使用__toString)。可选的query_builder选项需要一个QueryBuilder实例或封装的检索repository作为参数,并返回QueryBuilder用来得到选择。如果没有设置则将使用所有实体。