概述 快速入门 教程 手册 最佳实践 组件 参考 贡献

发布于 2015-08-27 16:26:33 | 387 次阅读 | 评论: 0 | 来源: 网络整理

One of the most common and challenging tasks for any application involves persisting and reading information to and from a database. Although the Symfony full-stack framework doesn’t integrate any ORM by default, the Symfony Standard Edition, which is the most widely used distribution, comes integrated with Doctrine, a library whose sole goal is to give you powerful tools to make this easy. In this chapter, you’ll learn the basic philosophy behind Doctrine and see how easy working with a database can be.

注解

Doctrine is totally decoupled from Symfony and using it is optional. This chapter is all about the Doctrine ORM, which aims to let you map objects to a relational database (such as MySQL, PostgreSQL or Microsoft SQL). If you prefer to use raw database queries, this is easy, and explained in the “如何使用Doctrine DBAL” cookbook entry.

You can also persist data to MongoDB using Doctrine ODM library. For more information, read the “DoctrineMongoDBBundle” documentation.

简单示例:产品类

The easiest way to understand how Doctrine works is to see it in action. In this section, you’ll configure your database, create a Product object, persist it to the database and fetch it back out.

配置数据库

Before you really begin, you’ll need to configure your database connection information. By convention, this information is usually configured in an app/config/parameters.yml file:

# app/config/parameters.yml
parameters:
    database_driver:    pdo_mysql
    database_host:      localhost
    database_name:      test_project
    database_user:      root
    database_password:  password

# ...

注解

Defining the configuration via parameters.yml is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Doctrine:

  • YAML
    # app/config/config.yml
    doctrine:
        dbal:
            driver:   "%database_driver%"
            host:     "%database_host%"
            dbname:   "%database_name%"
            user:     "%database_user%"
            password: "%database_password%"
    
  • XML
    <!-- 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="%database_driver%"
                host="%database_host%"
                dbname="%database_name%"
                user="%database_user%"
                password="%database_password%" />
        </doctrine:config>
    </container>
    
  • PHP
    // app/config/config.php
    $configuration->loadFromExtension('doctrine', array(
        'dbal' => array(
            'driver'   => '%database_driver%',
            'host'     => '%database_host%',
            'dbname'   => '%database_name%',
            'user'     => '%database_user%',
            'password' => '%database_password%',
        ),
    ));
    

By separating the database information into a separate file, you can easily keep different versions of the file on each server. You can also easily store database configuration (or any sensitive information) outside of your project, like inside your Apache configuration, for example. For more information, see 如何将Service Container的参数暴露到外部.

Now that Doctrine knows about your database, you can have it create the database for you:

$ php app/console doctrine:database:create

Setting up the Database to be UTF8

One mistake even seasoned developers make when starting a Symfony project is forgetting to set up default charset and collation on their database, ending up with latin type collations, which are default for most databases. They might even remember to do it the very first time, but forget that it’s all gone after running a relatively common command during development:

$ php app/console doctrine:database:drop --force
$ php app/console doctrine:database:create

There’s no way to configure these defaults inside Doctrine, as it tries to be as agnostic as possible in terms of environment configuration. One way to solve this problem is to configure server-level defaults.

Setting UTF8 defaults for MySQL is as simple as adding a few lines to your configuration file (typically my.cnf):

[mysqld]
collation-server = utf8_general_ci
character-set-server = utf8

注解

If you want to use SQLite as your database, you need to set the path where your database file should be stored:

  • YAML
    # app/config/config.yml
    doctrine:
        dbal:
            driver: pdo_sqlite
            path: "%kernel.root_dir%/sqlite.db"
            charset: UTF8
    
  • XML
    <!-- 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>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('doctrine', array(
        'dbal' => array(
            'driver'  => 'pdo_sqlite',
            'path'    => '%kernel.root_dir%/sqlite.db',
            'charset' => 'UTF-8',
        ),
    ));
    

创建Entity类

Suppose you’re building an application where products need to be displayed. Without even thinking about Doctrine or databases, you already know that you need a Product object to represent those products. Create this class inside the Entity directory of your AppBundle:

// src/AppBundle/Entity/Product.php
namespace AppBundleEntity;

class Product
{
    protected $name;
    protected $price;
    protected $description;
}

The class - often called an “entity”, meaning a basic class that holds data - is simple and helps fulfill the business requirement of needing products in your application. This class can’t be persisted to a database yet - it’s just a simple PHP class.

小技巧

Once you learn the concepts behind Doctrine, you can have Doctrine create simple entity classes for you. This will ask you interactive questions to help you build any entity:

$ php app/console doctrine:generate:entity

添加映射信息

Doctrine allows you to work with databases in a much more interesting way than just fetching rows of a column-based table into an array. Instead, Doctrine allows you to persist entire objects to the database and fetch entire objects out of the database. This works by mapping a PHP class to a database table, and the properties of that PHP class to columns on the table:

For Doctrine to be able to do this, you just have to create “metadata”, or configuration that tells Doctrine exactly how the Product class and its properties should be mapped to the database. This metadata can be specified in a number of different formats including YAML, XML or directly inside the Product class via annotations:

  • Annotations
    // src/AppBundle/Entity/Product.php
    namespace AppBundleEntity;
    
    use DoctrineORMMapping as ORM;
    
    /**
     * @ORMEntity
     * @ORMTable(name="product")
     */
    class Product
    {
        /**
         * @ORMColumn(type="integer")
         * @ORMId
         * @ORMGeneratedValue(strategy="AUTO")
         */
        protected $id;
    
        /**
         * @ORMColumn(type="string", length=100)
         */
        protected $name;
    
        /**
         * @ORMColumn(type="decimal", scale=2)
         */
        protected $price;
    
        /**
         * @ORMColumn(type="text")
         */
        protected $description;
    }
    
  • YAML
    # src/AppBundle/Resources/config/doctrine/Product.orm.yml
    AppBundleEntityProduct:
        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
    
  • XML
    <!-- 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="AppBundleEntityProduct" 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>
    

注解

A bundle can accept only one metadata definition format. For example, it’s not possible to mix YAML metadata definitions with annotated PHP entity class definitions.

小技巧

The table name is optional and if omitted, will be determined automatically based on the name of the entity class.

Doctrine allows you to choose from a wide variety of different field types, each with their own options. For information on the available field types, see the Doctrine属性类型参考 section.

参见

You can also check out Doctrine’s Basic Mapping Documentation for all details about mapping information. If you use annotations, you’ll need to prepend all annotations with ORM (e.g. ORMColumn(...)), which is not shown in Doctrine’s documentation. You’ll also need to include the use DoctrineORMMapping as ORM; statement, which imports the ORM annotations prefix.

警告

Be careful that your class name and properties aren’t mapped to a protected SQL keyword (such as group or user). For example, if your entity class name is Group, then, by default, your table name will be group, which will cause an SQL error in some engines. See Doctrine’s Reserved SQL keywords documentation on how to properly escape these names. Alternatively, if you’re free to choose your database schema, simply map to a different table name or column name. See Doctrine’s Persistent classes and Property Mapping documentation.

注解

When using another library or program (e.g. Doxygen) that uses annotations, you should place the @IgnoreAnnotation annotation on the class to indicate which annotations Symfony should ignore.

For example, to prevent the @fn annotation from throwing an exception, add the following:

/**
 * @IgnoreAnnotation("fn")
 */
class Product
// ...

生成Getters和Setters

Even though Doctrine now knows how to persist a Product object to the database, the class itself isn’t really useful yet. Since Product is just a regular PHP class, you need to create getter and setter methods (e.g. getName(), setName()) in order to access its properties (since the properties are protected). Fortunately, Doctrine can do this for you by running:

$ php app/console doctrine:generate:entities AppBundle/Entity/Product

This command makes sure that all the getters and setters are generated for the Product class. This is a safe command - you can run it over and over again: it only generates getters and setters that don’t exist (i.e. it doesn’t replace your existing methods).

警告

Keep in mind that Doctrine’s entity generator produces simple getters/setters. You should check generated entities and adjust getter/setter logic to your own needs.

More about doctrine:generate:entities

With the doctrine:generate:entities command you can:

  • 生成Getters和Setters;

  • generate repository classes configured with the @ORMEntity(repositoryClass="...") annotation;
  • generate the appropriate constructor for 1:n and n:m relations.

The doctrine:generate:entities command saves a backup of the original Product.php named Product.php~. In some cases, the presence of this file can cause a “Cannot redeclare class” error. It can be safely removed. You can also use the --no-backup option to prevent generating these backup files.

Note that you don’t need to use this command. Doctrine doesn’t rely on code generation. Like with normal PHP classes, you just need to make sure that your protected/private properties have getter and setter methods. Since this is a common thing to do when using Doctrine, this command was created.

You can also generate all known entities (i.e. any PHP class with Doctrine mapping information) of a bundle or an entire namespace:

# generates all entities in the AppBundle
$ php app/console doctrine:generate:entities AppBundle

# generates all entities of bundles in the Acme namespace
$ php app/console doctrine:generate:entities Acme

注解

Doctrine doesn’t care whether your properties are protected or private, or whether you have a getter or setter function for a property. The getters and setters are generated here only because you’ll need them to interact with your PHP object.

创建数据库表

You now have a usable Product class with mapping information so that Doctrine knows exactly how to persist it. Of course, you don’t yet have the corresponding product table in your database. Fortunately, Doctrine can automatically create all the database tables needed for every known entity in your application. To do this, run:

$ php app/console doctrine:schema:update --force

小技巧

Actually, this command is incredibly powerful. It compares what your database should look like (based on the mapping information of your entities) with how it actually looks, and generates the SQL statements needed to update the database to where it should be. In other words, if you add a new property with mapping metadata to Product and run this task again, it will generate the “alter table” statement needed to add that new column to the existing product table.

An even better way to take advantage of this functionality is via migrations, which allow you to generate these SQL statements and store them in migration classes that can be run systematically on your production server in order to track and migrate your database schema safely and reliably.

Your database now has a fully-functional product table with columns that match the metadata you’ve specified.

将对象保存到数据库

Now that you have a mapped Product entity and corresponding product table, you’re ready to persist data to the database. From inside a controller, this is pretty easy. Add the following method to the DefaultController of the bundle:

// src/AppBundle/Controller/DefaultController.php

// ...
use AppBundleEntityProduct;
use SymfonyComponentHttpFoundationResponse;

// ...
public function createAction()
{
    $product = new Product();
    $product->setName('A Foo Bar');
    $product->setPrice('19.99');
    $product->setDescription('Lorem ipsum dolor');

    $em = $this->getDoctrine()->getManager();

    $em->persist($product);
    $em->flush();

    return new Response('Created product id '.$product->getId());
}

注解

If you’re following along with this example, you’ll need to create a route that points to this action to see it work.

小技巧

This article shows working with Doctrine from within a controller by using the getDoctrine() method of the controller. This method is a shortcut to get the doctrine service. You can work with Doctrine anywhere else by injecting that service in the service. See 服务容器(Service Container) for more on creating your own services.

Take a look at the previous example in more detail:

  • lines 10-13 In this section, you instantiate and work with the $product object like any other, normal PHP object.
  • line 15 This line fetches Doctrine’s entity manager object, which is responsible for handling the process of persisting and fetching objects to and from the database.
  • line 16 The persist() method tells Doctrine to “manage” the $product object. This does not actually cause a query to be made to the database (yet).
  • line 17 When the flush() method is called, Doctrine looks through all of the objects that it’s managing to see if they need to be persisted to the database. In this example, the $product object has not been persisted yet, so the entity manager executes an INSERT query and a row is created in the product table.

注解

In fact, since Doctrine is aware of all your managed entities, when you call the flush() method, it calculates an overall changeset and executes the queries in the correct order. It utilizes cached prepared statement to slightly improve the performance. For example, if you persist a total of 100 Product objects and then subsequently call flush(), Doctrine will execute 100 INSERT queries using a single prepared statement object.

When creating or updating objects, the workflow is always the same. In the next section, you’ll see how Doctrine is smart enough to automatically issue an UPDATE query if the record already exists in the database.

小技巧

Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. “fixture data”). For information, see the “DoctrineFixturesBundle” documentation.

从数据库获取对象

Fetching an object back out of the database is even easier. For example, suppose you’ve configured a route to display a specific Product based on its id value:

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    // ... do something, like pass the $product object into a template
}

小技巧

You can achieve the equivalent of this without writing any code by using the @ParamConverter shortcut. See the FrameworkExtraBundle documentation for more details.

When you query for a particular type of object, you always use what’s known as its “repository”. You can think of a repository as a PHP class whose only job is to help you fetch entities of a certain class. You can access the repository object for an entity class via:

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

注解

The AppBundle:Product string is a shortcut you can use anywhere in Doctrine instead of the full class name of the entity (i.e. AppBundleEntityProduct). As long as your entity lives under the Entity namespace of your bundle, this will work.

Once you have your repository, you have access to all sorts of helpful methods:

// query by the primary key (usually "id")
$product = $repository->find($id);

// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');

// find *all* products
$products = $repository->findAll();

// find a group of products based on an arbitrary column value
$products = $repository->findByPrice(19.99);

注解

Of course, you can also issue complex queries, which you’ll learn more about in the 查找对象 section.

You can also take advantage of the useful findBy and findOneBy methods to easily fetch objects based on multiple conditions:

// query for one product matching by name and price
$product = $repository->findOneBy(
    array('name' => 'foo', 'price' => 19.99)
);

// query for all products matching the name, ordered by price
$products = $repository->findBy(
    array('name' => 'foo'),
    array('price' => 'ASC')
);

小技巧

When you render any page, you can see how many queries were made in the bottom right corner of the web debug toolbar.

#

If you click the icon, the profiler will open, showing you the exact queries that were made.

The icon will turn yellow if there were more than 50 queries on the page. This could indicate that something is not correct.

更新对象

Once you’ve fetched an object from Doctrine, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller:

public function updateAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('AppBundle:Product')->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    $product->setName('New product name!');
    $em->flush();

    return $this->redirectToRoute('homepage');
}

Updating an object involves just three steps:

  1. fetching the object from Doctrine;
  2. modifying the object;
  3. calling flush() on the entity manager

Notice that calling $em->persist($product) isn’t necessary. Recall that this method simply tells Doctrine to manage or “watch” the $product object. In this case, since you fetched the $product object from Doctrine, it’s already managed.

删除对象

Deleting an object is very similar, but requires a call to the remove() method of the entity manager:

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

As you might expect, the remove() method notifies Doctrine that you’d like to remove the given object from the database. The actual DELETE query, however, isn’t actually executed until the flush() method is called.

查找对象

You’ve already seen how the repository object allows you to run basic queries without any work:

$repository->find($id);

$repository->findOneByName('Foo');

Of course, Doctrine also allows you to write more complex queries using the Doctrine Query Language (DQL). DQL is similar to SQL except that you should imagine that you’re querying for one or more objects of an entity class (e.g. Product) instead of querying for rows on a table (e.g. product).

When querying in Doctrine, you have two options: writing pure Doctrine queries or using Doctrine’s Query Builder.

使用Doctrine的Query Builder查找对象

Imagine that you want to query for products, but only return products that cost more than 19.99, ordered from cheapest to most expensive. You can use Doctrine’s QueryBuilder for this:

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

$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();

$products = $query->getResult();

The QueryBuilder object contains every method necessary to build your query. By calling the getQuery() method, the query builder returns a normal Query object, which can be used to get the result of the query.

小技巧

Take note of the setParameter() method. When working with Doctrine, it’s always a good idea to set any external values as “placeholders” (:price in the example above) as it prevents SQL injection attacks.

The getResult() method returns an array of results. To get only one result, you can use getSingleResult() (which throws an exception if there is no result) or getOneOrNullResult():

$product = $query->getOneOrNullResult();

For more information on Doctrine’s Query Builder, consult Doctrine’s Query Builder documentation.

使用DQL查询对象

Instead of using the QueryBuilder, you can alternatively write the queries directly using DQL:

$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();

If you’re comfortable with SQL, then DQL should feel very natural. The biggest difference is that you need to think in terms of “objects” instead of rows in a database. For this reason, you select from the AppBundle:Product object and then alias it as p (as you see, this is equal to what you already did in the previous section).

The DQL syntax is incredibly powerful, allowing you to easily join between entities (the topic of relations will be covered later), group, etc. For more information, see the official Doctrine Query Language documentation.

自定义Repository类

In the previous sections, you began constructing and using more complex queries from inside a controller. In order to isolate, test and reuse these queries, it’s a good practice to create a custom repository class for your entity and add methods with your query logic there.

To do this, add the name of the repository class to your mapping definition:

  • Annotations
    // src/AppBundle/Entity/Product.php
    namespace AppBundleEntity;
    
    use DoctrineORMMapping as ORM;
    
    /**
     * @ORMEntity(repositoryClass="AppBundleEntityProductRepository")
     */
    class Product
    {
        //...
    }
    
  • YAML
    # src/AppBundle/Resources/config/doctrine/Product.orm.yml
    AppBundleEntityProduct:
        type: entity
        repositoryClass: AppBundleEntityProductRepository
        # ...
    
  • XML
    <!-- 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="AppBundleEntityProduct"
            repository-class="AppBundleEntityProductRepository">
    
            <!-- ... -->
        </entity>
    </doctrine-mapping>
    

Doctrine can generate the repository class for you by running the same command used earlier to generate the missing getter and setter methods:

$ php app/console doctrine:generate:entities AppBundle

Next, add a new method - findAllOrderedByName() - to the newly generated repository class. This method will query for all the Product entities, ordered alphabetically.

// src/AppBundle/Entity/ProductRepository.php
namespace AppBundleEntity;

use DoctrineORMEntityRepository;

class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery(
                'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
            )
            ->getResult();
    }
}

小技巧

The entity manager can be accessed via $this->getEntityManager() from inside the repository.

You can use this new method just like the default finder methods of the repository:

$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('AppBundle:Product')
    ->findAllOrderedByName();

注解

When using a custom repository class, you still have access to the default finder methods such as find() and findAll().

Entity之间的关联

Suppose that the products in your application all belong to exactly one “category”. In this case, you’ll need a Category object and a way to relate a Product object to a Category object. Start by creating the Category entity. Since you know that you’ll eventually need to persist the class through Doctrine, you can let Doctrine create the class for you.

$ php app/console doctrine:generate:entity 
    --entity="AppBundle:Category" 
    --fields="name:string(255)"

This task generates the Category entity for you, with an id field, a name field and the associated getter and setter functions.

关联配置

To relate the Category and Product entities, start by creating a products property on the Category class:

  • Annotations
    // src/AppBundle/Entity/Category.php
    
    // ...
    use DoctrineCommonCollectionsArrayCollection;
    
    class Category
    {
        // ...
    
        /**
         * @ORMOneToMany(targetEntity="Product", mappedBy="category")
         */
        protected $products;
    
        public function __construct()
        {
            $this->products = new ArrayCollection();
        }
    }
    
  • YAML
    # src/AppBundle/Resources/config/doctrine/Category.orm.yml
    AppBundleEntityCategory:
        type: entity
        # ...
        oneToMany:
            products:
                targetEntity: Product
                mappedBy: category
        # don't forget to init the collection in the __construct() method
        # of the entity
    
  • XML
    <!-- src/AppBundle/Resources/config/doctrine/Category.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="AppBundleEntityCategory">
            <!-- ... -->
            <one-to-many
                field="products"
                target-entity="Product"
                mapped-by="category" />
    
            <!--
                don't forget to init the collection in
                the __construct() method of the entity
            -->
        </entity>
    </doctrine-mapping>
    

First, since a Category object will relate to many Product objects, a products array property is added to hold those Product objects. Again, this isn’t done because Doctrine needs it, but instead because it makes sense in the application for each Category to hold an array of Product objects.

注解

The code in the __construct() method is important because Doctrine requires the $products property to be an ArrayCollection object. This object looks and acts almost exactly like an array, but has some added flexibility. If this makes you uncomfortable, don’t worry. Just imagine that it’s an array and you’ll be in good shape.

小技巧

The targetEntity value in the decorator used above can reference any entity with a valid namespace, not just entities defined in the same namespace. To relate to an entity defined in a different class or bundle, enter a full namespace as the targetEntity.

Next, since each Product class can relate to exactly one Category object, you’ll want to add a $category property to the Product class:

  • Annotations
    // src/AppBundle/Entity/Product.php
    
    // ...
    class Product
    {
        // ...
    
        /**
         * @ORMManyToOne(targetEntity="Category", inversedBy="products")
         * @ORMJoinColumn(name="category_id", referencedColumnName="id")
         */
        protected $category;
    }
    
  • YAML
    # src/AppBundle/Resources/config/doctrine/Product.orm.yml
    AppBundleEntityProduct:
        type: entity
        # ...
        manyToOne:
            category:
                targetEntity: Category
                inversedBy: products
                joinColumn:
                    name: category_id
                    referencedColumnName: id
    
  • XML
    <!-- 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="AppBundleEntityProduct">
            <!-- ... -->
            <many-to-one
                field="category"
                target-entity="Category"
                inversed-by="products"
                join-column="category">
    
                <join-column name="category_id" referenced-column-name="id" />
            </many-to-one>
        </entity>
    </doctrine-mapping>
    

Finally, now that you’ve added a new property to both the Category and Product classes, tell Doctrine to generate the missing getter and setter methods for you:

$ php app/console doctrine:generate:entities AppBundle

Ignore the Doctrine metadata for a moment. You now have two classes - Category and Product with a natural one-to-many relationship. The Category class holds an array of Product objects and the Product object can hold one Category object. In other words - you’ve built your classes in a way that makes sense for your needs. The fact that the data needs to be persisted to a database is always secondary.

Now, look at the metadata above the $category property on the Product class. The information here tells Doctrine that the related class is Category and that it should store the id of the category record on a category_id field that lives on the product table. In other words, the related Category object will be stored on the $category property, but behind the scenes, Doctrine will persist this relationship by storing the category’s id value on a category_id column of the product table.

The metadata above the $products property of the Category object is less important, and simply tells Doctrine to look at the Product.category property to figure out how the relationship is mapped.

Before you continue, be sure to tell Doctrine to add the new category table, and product.category_id column, and new foreign key:

$ php app/console doctrine:schema:update --force

注解

This task should only be really used during development. For a more robust method of systematically updating your production database, read about migrations.

更多关于关联的信息

This section has been an introduction to one common type of entity relationship, the one-to-many relationship. For more advanced details and examples of how to use other types of relations (e.g. one-to-one, many-to-many), see Doctrine’s Association Mapping Documentation.

注解

If you’re using annotations, you’ll need to prepend all annotations with ORM (e.g. ORMOneToMany), which is not reflected in Doctrine’s documentation. You’ll also need to include the use DoctrineORMMapping as ORM; statement, which imports the ORM annotations prefix.

配置

Doctrine is highly configurable, though you probably won’t ever need to worry about most of its options. To find out more about configuring Doctrine, see the Doctrine section of the config reference.

运行周期内执行的回调函数

Sometimes, you need to perform an action right before or after an entity is inserted, updated, or deleted. These types of actions are known as “lifecycle” callbacks, as they’re callback methods that you need to execute during different stages of the lifecycle of an entity (e.g. the entity is inserted, updated, deleted, etc).

If you’re using annotations for your metadata, start by enabling the lifecycle callbacks. This is not necessary if you’re using YAML or XML for your mapping.

/**
 * @ORMEntity()
 * @ORMHasLifecycleCallbacks()
 */
class Product
{
    // ...
}

Now, you can tell Doctrine to execute a method on any of the available lifecycle events. For example, suppose you want to set a createdAt date column to the current date, only when the entity is first persisted (i.e. inserted):

  • Annotations
    // src/AppBundle/Entity/Product.php
    
    /**
     * @ORMPrePersist
     */
    public function setCreatedAtValue()
    {
        $this->createdAt = new DateTime();
    }
    
  • YAML
    # src/AppBundle/Resources/config/doctrine/Product.orm.yml
    AppBundleEntityProduct:
        type: entity
        # ...
        lifecycleCallbacks:
            prePersist: [setCreatedAtValue]
    
  • XML
    <!-- 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="AppBundleEntityProduct">
            <!-- ... -->
            <lifecycle-callbacks>
                <lifecycle-callback type="prePersist" method="setCreatedAtValue" />
            </lifecycle-callbacks>
        </entity>
    </doctrine-mapping>
    

注解

The above example assumes that you’ve created and mapped a createdAt property (not shown here).

Now, right before the entity is first persisted, Doctrine will automatically call this method and the createdAt field will be set to the current date.

There are several other lifecycle events that you can hook into. For more information on other lifecycle events and lifecycle callbacks in general, see Doctrine’s Lifecycle Events documentation.

Lifecycle Callbacks and Event Listeners

Notice that the setCreatedAtValue() method receives no arguments. This is always the case for lifecycle callbacks and is intentional: lifecycle callbacks should be simple methods that are concerned with internally transforming data in the entity (e.g. setting a created/updated field, generating a slug value).

If you need to do some heavier lifting - like performing logging or sending an email - you should register an external class as an event listener or subscriber and give it access to whatever resources you need. For more information, see 如何注册Event Listener以及Subscriber.

Doctrine属性类型参考

Doctrine comes with numerous field types available. Each of these maps a PHP data type to a specific column type in whatever database you’re using. For each field type, the Column can be configured further, setting the length, nullable behavior, name and other options. To see a list of all available types and more information, see Doctrine’s Mapping Types documentation.

总结

With Doctrine, you can focus on your objects and how they’re used in your application and worry about database persistence second. This is because Doctrine allows you to use any PHP object to hold your data and relies on mapping metadata information to map an object’s data to a particular database table.

And even though Doctrine revolves around a simple concept, it’s incredibly powerful, allowing you to create complex queries and subscribe to events that allow you to take different actions as objects go through their persistence lifecycle.

更多信息

For more information about Doctrine, see the Doctrine section of the cookbook. Some useful articles might be:

最新网友评论  共有(0)条评论 发布评论 返回顶部

Copyright © 2007-2017 PHPERZ.COM All Rights Reserved   冀ICP备14009818号  版权声明  广告服务