數據庫和Doctrine(轉載自http://www.111cn.net/phper/332/85987.htm)

  對於任何應用程序來講最爲廣泛最具挑戰性的任務,就是從數據庫中 讀取和持久化數據信息。儘管symfony完整的框架沒有默認集成ORM,可是symfony標準版,集成了不少程序,還自帶集成了Doctrine這樣 一個庫,主要的目的是給開發者一個強大的工具,讓你工做起來更加容易。在本章,你會學會doctrine的基本理念而且可以瞭解如何輕鬆使用數據庫。php

  Doctrine能夠徹底脫離symfony使用,而且在symfony中是否使用也是可選的。本章主要了解Doctrine的ORM,其目的是讓你的對 象映射到數據庫中(如MySQL, PostgreSQL和Microsoft SQL)。若是你喜歡使用原始的數據庫查詢,這很容易,能夠了解cookbook 中的」How to Use Doctrine DBAL「。
你也可使用Doctrine ODM庫將數據持久化到MongoDB。更多信息請參閱」DoctrineMongoDBBundle「。html

一個簡單的例子:一個產品
瞭解Doctrine是如何工做的最簡單的方式就是看一個實際的應用。在本章,你須要配置你的數據庫,建立一個Product對象,持久化它到數據庫而且把它抓取回來。mysql

配置數據庫程序員

在你真正開始以前,你須要配置你的數據庫連接信息。按照慣例,這些信息一般配置在app/config/parameters.yml文件中:web

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

 將配置信息定義到parameters.yml僅僅是一個慣例。定義在該文件中的配置信息將會被主配置文件在安裝Doctrine時引用。sql

# app/config/config.yml
doctrine:
    dbal:
        driver:   "%database_driver%"
        host:     "%database_host%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"

經過把數據庫信息分離到一個特定的文件中,你能夠很容易的爲每一個服務器保存不一樣的版本。你也能夠在項目外輕鬆存儲數據庫配置(一些敏感信息),就像apache配置同樣。更多信息請參閱How to Set external Parameters in the Service Container.
如今Doctrine知道你的數據庫配置了,你能夠用它來建立一個數據庫了。數據庫

$ php app/console doctrine:database:create

設置數據庫爲UTF8
即使對於經驗豐富的程序員來講,一個常犯的錯誤是,在Symfony項目開始後,忘記設置他們的數據庫默認字符集和校對規則,僅把大部分數據庫給出的latin類型的校對做爲默認。他們也許在第一次操做時會記得,但到了後面敲打兩行相關的常規命令以後,就徹底忘掉了。apache

$ php app/console doctrine:database:drop --force
$ php app/console doctrine:database:create
在Doctrine裏直接指派默認字符集是不可能的,由於doctrine會根據環境配置,儘量多地去適應各類「不可知」情形。解決辦法之一,是去配置「服務器級別」的默認信息。
設置UTF8爲MySql的默認字符集是很是簡單的,只要在數據庫配置文件中加幾行代碼就能夠了(通常是my.cnf文件)編程


[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
咱們推薦避免使用Mysql的uft8字符集,由於它並不兼容4-byte unicode字符,若是字符串中有這種字符會被清空。不過這種狀況被修復了,參考《新型utf8mb4字符集》數組

---------------------------------------------新建數據庫這段還不如直接在mysql裏面操做更方便-----------------------------------------------

若是你想要使用SQLite做爲數據庫,你須要設置path爲你的數據庫路徑

# app/config/config.yml
doctrine:
    dbal:
        driver: pdo_sqlite
        path: "%kernel.root_dir%/sqlite.db"
        charset: UTF8
 

建立一個實體類

假設你建立一個應用程序,其中有些產品須要展現。即時不考慮Doctrine或者數據庫,你也應該知道你須要一個Product對象來表現這些產品。在你的AppBundle的Entity目錄下建立一個類。

// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
 
class Product
{
    protected $name;
    protected $price;
    protected $description;
}

這樣的類常常被稱爲「Entity」,意味着一個基礎類保存數據。它們簡單來知足你應用程序的業務須要。不過如今它還不能被保存到數據庫中,由於如今它只不過仍是個簡單的PHP類。

一旦你學習了Doctrine背後的概念,你可讓Doctrine來爲你建立實體類。他會問你一些問題來建立entity:

$ php app/console doctrine:generate:entity

 添加映射信息

Doctrine容許你使用一種更加有趣的方式對數據庫進行操做,而不是隻是獲取基於列表的行到數組中。Doctrine容許你保存整個對象到數據庫或者把對象從數據庫中取出。這些都是經過映射PHP類到一個數據庫表,PHP類的屬性對應數據庫表的列來實現的。

../_images/doctrine_image_1.png

由於Doctrine可以作這些,因此你僅僅只須要建立一個meatdata,或者配置告訴Doctrine的Product類和它的屬性應該如何映射到數據庫。這些metadata能夠被定義成各類格式,包括YAML,XML或者經過聲明直接定義到Product類中。

annotations:


// 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")
     */
    protected $id;
 
    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;
 
    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    protected $price;
 
    /**
     * @ORM\Column(type="text")
     */
    protected $description;
}

一個bundle只能夠接受一種metadata定義格式。好比,不能把YAML定義的metadata和聲明PHP實體類一塊兒混用。
表名是可選的,若是省略,將基於entity類的名稱自動肯定。
Doctrine容許你去選擇各類不一樣的字段類型,每一個字段都有本身的配置。有關字段類型信息,請看Doctrine Field Types Reference。

你也能夠查看Doctrine官方文檔Basic Mapping Documentation關於映射信息的全部細節。若是你使用annotations,你須要全部的註釋都有ORM(例如 ORM\Column()),這些doctrine模板並無。你還須要去引入use Doctrine\ORM\Mapping as ORM;聲明,它是用來引進ORM註冊前綴的。
當心你的類名和屬性極可能就被映射到一個受保護的SQL字段(如group和user)。舉例,若是你的entity類名稱爲Group,那麼,在默認情 況下,你的表名爲group,在一些引擎中可能致使SQL錯誤。請查看 Reserved SQL keywords documentation,他會告訴你如何正確的規避這些名稱。另外,你能夠自由簡單的映射到不一樣的表名和字段名,來選擇你的數據庫綱要。請查看 Doctrine的Persistent classes和Property Mapping文檔。
當使用其餘的庫或者程序(例如 Doxygen)它們使用了註釋,你應該把@IgnoreAnnotation註釋添加到該類上來告訴Symfony忽略它們。
好比咱們要阻止@fn 聲明拋出異常,能夠這樣:

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

生產Getters和Setters
儘管Doctrine如今知道了如何持久化Product對象到數據庫,可是類自己是否是有用呢。由於Product僅僅是一個標準的PHP類,你須要創 建getter和setter方法(好比getName(),setName())來訪問它的屬性(由於它的屬性是protected),幸運的是 Doctrine能夠爲咱們作這些:

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

該命令能夠確保Product類全部的getter和setter都被生成。這是一個安全的命令行,你能夠屢次運行它,它只會生成那些不存在的getters和setters,而不會替換已有的。

請記住doctrine entity引擎生產簡單的getters/setters。你應該檢查生成的實體,調整getter/setter邏輯爲本身想要的。

關於doctrine:generate:entities命令
用它你能夠生成getters和setters。
用它在配置@ORM\Entity(repositoryClass=」…」)聲明的狀況下,生成repository類。
用它能夠爲1:n或者n:m生成合適的構造器。
該命令會保存一個原來Product.php文件的備份Product.php~。 有些時候可也可以會形成「不能從新聲明類」錯誤,你能夠放心的刪除它,來消除錯誤。您還可使用–no-backup選項,來防止產生這些配置文件。
固然你沒有必要依賴於該命令行,Doctrine不依賴於代碼生成,像標準的PHP類,你只須要保證它的protected/private屬性擁有getter和setter方法便可。主要因爲用命令行去建立是,一種常見事。
你也能夠爲一個bundle或者整個實體命名空間內的全部已知實體(任何包含Doctrine映射聲明的PHP類)來生成getter和setter:

# 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不關心你的屬性是protected仍是private,或者這些屬性是否有getter或setter。之因此生成這些getter或者setter徹底是由於你須要跟你的PHP對象進行交流須要它們。
 

建立數據庫表和模式
如今咱們有了一個可用的Product類和它的映射信息,因此Doctrine知道如何持久化它。固然,如今Product尚未相應的product數據庫表在數據庫中。幸運的是,Doctrine能夠自動建立全部的數據庫表。

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

 說真的,這條命令是出奇的強大。它會基於你的entities的映射信息,來比較如今的數據庫,並生成所須要的新數據庫的更新SQl語句。換句話說,如 果你想添加一個新的屬性映射元數據到Product並運行該任務,它將生成一個alert table 語句來添加新的列到已經存在的product表中。
一個更好的發揮這一優點的功能是經過migrations,它容許你生成這些SQL語句。並存儲到一個遷移類,並能有組織的運行在你的生產環境中,系統爲了安全可靠地跟蹤和遷移數據庫。
如今你的數據庫中有了一個全功能的product表,它的每一個列都會被映射到你指定的元數據。

持久化對象到數據庫
如今咱們有了一個Product實體和與之映射的product數據庫表。你能夠把數據持久化到數據庫裏。在Controller內,它很是簡單。添加下面的方法到bundle的DefaultController中。

 

// src/AppBundle/Controller/DefaultController.php
 
// ...
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
 
// ...
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());
}

若是你想演示這個案例,你須要去建立一個路由指向這個action,讓他工做。
本文展現了在控制器中使用Doctrine的getDoctrine()方法。這個方法是獲取doctrine服務最便捷的方式。你能在服務中的任何其餘地方使用doctrine注入該服務。更多關於常見本身的服務信息,請參閱Service Container。
在看看前面例子的詳情:

在本節10-13行,你實例化$product對象,就像其餘任何普通的php對象同樣。

15行獲取doctrine實體管理對象,這是負責處理數據庫持久化過程和讀取對象的。

16行persist()方法告訴Doctrine去「管理」這個$product對象。尚未在數據庫中使用過語句。

17行黨這個flush()方法被調用,Doctrine會查看它管理的全部對象,是否須要被持久化到數據庫。在本例子中,這個$product對象尚未持久化,因此這個entity管理就會執行一個insert語句而且會在product表中建立一行數據。

事實上,Doctrine瞭解你全部的被管理的實體,當你調用flush()方法時,它會計算出全部的變化,並執行最有效的查詢可能。 他利用準備好的緩存略微提升性能。好比,你要持久化老是爲100的產品對象,而後調用flush()方法。Doctrine會建立一個惟一的預備語句並重 復使用它插入。
在建立和更新對象時,工做流是相同的。在下一節中,若是記錄已經存在數據庫中,您將看到Doctrine如何聰明的自動發出一個Update語句。

Doctrine提供了一個類庫容許你經過編程,加載測試數據到你的項目。該類庫爲 DoctrineFixturesBundle(http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html)

從數據庫中獲取對象

從數據庫中獲取對象更容易,舉個例子,假如你配置了一個路由來,用它的ID顯示特定的product。

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
}

你可使用@ParamConverter註釋不用編寫任何代碼就能夠實現一樣的功能。更多信息請查看FrameworkExtraBundle文檔。
當你查詢某個特定的產品時,你老是須要使用它的」respository」。你能夠認爲Respository是一個PHP類,它的惟一工做就是幫助你從某個特定類哪裏獲取實體。你能夠爲一個實體對象訪問一個repository對象,以下:

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

其中appBundle:Product是簡潔寫法,你能夠在Doctrine中任意使用它來替代實體類的全限定名稱(例如AppBundle\Entity\Product)。只要你的entity在你的bundle的Entity命名空間下它就會工做。你一旦有了Repository,你就能夠訪問其全部分類的幫助方法了。

// 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);

 固然,你也可使用複雜的查詢,想了解更多請閱讀Querying for Objects 。
你也能夠有效利用findBy和findOneBy方法的優點,很容易的基於多個條件來獲取對象。

// 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')
);

當你去渲染頁面,你能夠在網頁調試工具的右下角看到許多的查詢。
doctrine_web_debug_toolbar
若是你單機該圖標,分析頁面將打開,顯示你的精確查詢。
若是你的頁面查詢超過了50個它會變成黃色。這可能代表你的程序有問題。

更新對象
一旦你從Doctrine中獲取了一個對象,那麼更新它就變得很容易了。假設你有一個路由映射一個產品id到一個controller的updateaction。

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');
}

更新一個對象包括三步:

1.從Doctrine取出對象
2.修改對象
3.在實體管理者上調用flush()方法

注意調用 $em->persist($product) 在這裏沒有必要。咱們回想一下,調用該方法的目的主要是告訴Doctrine來管理或者「觀察」$product對象。在這裏,由於你已經取到了$product對象了,說明已經被管理了。

刪除對象

刪除一個對象,須要從實體管理者那裏調用remove()方法。

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

正如你想的那樣,remove()方法告訴Doctrine你想從數據庫中移除指定的實體。真正的刪除查詢沒有被真正的執行,直到flush()方法被調用。

查詢對象

你已經看到了repository對象容許你執行一些基本的查詢而不須要你作任何的工做。

$repository->find($id);
 
$repository->findOneByName('Foo');

固然,Doctrine 也容許你使用Doctrine Query Language(DQL)寫一些複雜的查詢,DQL相似於SQL,只是它用於查詢一個或者多個實體類的對象,而SQL則是查詢一個數據庫表中的行。

在Doctrinez中查詢時,你有兩種選擇:寫純Doctrine查詢 或者 使用Doctrine的查詢建立器。

 

使用Doctrine’s Query Builder查詢對象
假設你想查詢產品,須要返回價格高於19.99的產品,而且要求按價格從低到高排列。你可使用Doctrine的QueryBuilder:

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

QueryBuilder對象包含了建立查詢的全部必須的方法。經過調用getQuery()方法,查詢建立器將返回一個標準的Query對象。它跟咱們直接寫查詢對象效果相同。

記住setParameter()方法。當Doctrine工做時,外部的值,會經過「佔位符」(上面例子的:price)傳入,來防止SQL注入攻擊。
該getResult()方法返回一個結果數組。想要獲得一個結果,你可使用getSingleResult()(這個方法在沒有結果時會拋出一個異常)或者getOneOrNullResult():

$product = $query->getOneOrNullResult();

更多Doctrine’s Query Builder的信息請閱讀Query Builder。
 

使用DQL查詢對象

不愛使用QueryBuilder,你還能夠直接使用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();

若是你習慣了寫SQL,那麼對於DQL也應該不會感到陌生。它們之間最大的不一樣就是你須要思考對象,而不是數據庫錶行。正由於如此,因此你從AppBundle:Product選擇並給它定義別名p。(你看和上面完成的結果同樣)。

該DQL語法強大到使人難以置信,容許您輕鬆地在之間加入實體(稍後會介紹關係)、組等。更多信息請參閱Doctrine Query Language文檔。

自定義Repository類

在上面你已經開始在controller中建立和使用負責的查詢了。爲了隔離,測試和重用這些查詢,一個好的辦法是爲你的實體建立一個自定義的repository類並添加相關邏輯查詢方法。

要定義repository類,首先須要在你的映射定義中添加repository類的聲明:

// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
 */
class Product
{
    //...
}

而後經過運行跟以前生成丟失的getter和setter方法一樣的命令行,Doctrine會爲你自動生成repository類。

$ php app/console doctrine:generate:entities AppBundle

下面,添加一個新方法findAllOrderedByName() 到新生成的repository類。該方法將查詢全部的Product實體,並按照字符順序排序。

// src/AppBundle/Entity/ProductRepository.php
namespace AppBundle\Entity;
 
use Doctrine\ORM\EntityRepository;
 
class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery(
                'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
            )
            ->getResult();
    }
}

 在Repository類中能夠經過$this->getEntityManager()方法類獲取entity管理。
你就能夠像使用默認的方法同樣使用這個新定義的方法了:

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

當使用一個自定義的repository類時,你依然能夠訪問原有的默認查找方法,好比find() 和findAll()等。

實體的關係/關聯
假設你應用程序中的產品屬於一肯定的分類。這時你須要一個分類對象和一種把Product和Category對象聯繫在一塊兒的方式。首先咱們建立Category實體,咱們最終要經過Doctrine來對其進行持久化,因此咱們這裏讓Doctrine來幫咱們建立這個類。

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

該命令行爲你生成一個Category實體,包含id字段和name字段以及相關的getter和setter方法。

關係映射

關聯Category和Product兩個實體,首先在Category類中建立一個products屬性:

// src/AppBundle/Entity/Category.php
 
// ...
use Doctrine\Common\Collections\ArrayCollection;
 
class Category
{
    // ...
 
    /**
     * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
     */
    protected $products;
 
    public function __construct()
    {
        $this->products = new ArrayCollection();
    }
}

首先,因爲一個Category對象將涉及到多個Product對象,一個products數組屬性被添加到Category類保存這些 Product對象。其次,這不是由於Doctrine須要它,而是由於在應用程序中爲每個Category來保存一個Product數組很是有用。

代碼中__construct()方法很是重要,由於Doctrine須要$products屬性成爲一個ArrayCollection對象,它跟數組很是相似,但會靈活一些。若是這讓你感受不舒服,不用擔憂。試想他是一個數組,你會欣然接受它。
上面註釋所用的targetEntity 的值可使用合法的命名空間引用任何實體,而不只僅是定義在同一個類中的實體。 若是要關係一個定義在不一樣的類或者bundle中的實體則須要輸入徹底的命名空間做爲目標實體。
接下來,由於每一個Product類能夠關聯一個Category對象,全部添加一個$category屬性到Product類:

// src/AppBundle/Entity/Product.php
 
// ...
class Product
{
    // ...
 
    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;
}

到如今爲止,咱們添加了兩個新屬性到Category和Product類。如今告訴Doctrine來爲它們生成getter和setter方法。

$ php app/console doctrine:generate:entities AppBundle

咱們先不看Doctrine的元數據,你如今有兩個類Category和Product,而且擁有一個一對多的關係。該Category類包含一個 數組Product對象,Product包含一個Category對象。換句話說,你已經建立了你所須要的類了。事實上把這些須要的數據持久化到數據庫上 是次要的。

如今,讓咱們來看看在Product類中爲$category配置的元數據。它告訴Doctrine關係類是Category而且它須要保存 category的id到product表的category_id字段。換句話說,相關的分類對象將會被保存到$category屬性中,可是在底 層,Doctrine會經過存儲category的id值到product表的category_id列持久化它們的關係。

../_images/doctrine_image_2.png

Category類中$product屬性的元數據配置不是特別重要,它僅僅是告訴Doctrine去查找Product.category屬性來計算出關係映射是什麼。

在繼續以前,必定要告訴Doctrine添加一個新的category表和product.category_id列以及新的外鍵。

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

保存相關實體

如今讓咱們來看看Controller內的代碼如何處理:

 

// ...
 
use AppBundle\Entity\Category;
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
 
class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category->setName('Main Products');
 
        $product = new Product();
        $product->setName('Foo');
        $product->setPrice(19.99);
        $product->setDescription('Lorem ipsum dolor');
        // relate this product to the category
        $product->setCategory($category);
 
        $em = $this->getDoctrine()->getManager();
        $em->persist($category);
        $em->persist($product);
        $em->flush();
 
        return new Response(
            'Created product id: '.$product->getId()
            .' and category id: '.$category->getId()
        );
    }
}

如今,一個單獨的行被添加到category和product表中。新產品的product.categroy_id列被設置爲新category表中的id的值。Doctrine會爲你管理這些持久化關係。

獲取相關對象

當你須要獲取相關的對象時,你的工做流跟之前同樣。首先獲取$product對象,而後訪問它的相關Category

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->find($id);
 
    $categoryName = $product->getCategory()->getName();
 
    // ...
}

在這個例子中,你首先基於產品id查詢一個Product對象。他僅僅查詢產品數據並把數據給$product對象。接下來,當你調 用$product->getCategory()->getName() 時,Doctrine默默的爲你執行了第二次查詢,查找一個與該產品相關的category,它生成一個$category對象返回給你。

../_images/doctrine_image_3.png

重要的是你很容易的訪問到了product的相關category對象。可是category的數據並不會被取出來而直到你請求category的時候。這就是延遲加載。

你也能夠從其它方向進行查詢:

public function showProductsAction($id)
{
    $category = $this->getDoctrine()
        ->getRepository('AppBundle:Category')
        ->find($id);
 
    $products = $category->getProducts();
 
    // ...
}

在這種狀況下,一樣的事情發生了。你首先查查一個category對象,而後Doctrine製造了第二次查詢來獲取與之相關聯的全部Product對 象。只有在你調用->getProducts()時纔會執行一次。 $products變量是一個經過它的category_id的值跟給定的category對象相關聯的全部Product對象的集合。

關係和代理類

「延遲加載」成爲可能,是由於Doctrine返回一個代理對象來代替真正的對象:

$product = $this->getDoctrine()
    ->getRepository('AppBundle:Product')
    ->find($id);
 
$category = $product->getCategory();
 
// prints "Proxies\AppBundleEntityCategoryProxy"
echo get_class($category);

該代理對象繼承了Category對象,從外表到行爲都很是像category對象。所不一樣的是,經過這個代理對象,Doctrine能夠延遲查詢真正的Category對象數據,直到真正須要它時(調用$category->getName())。
Doctrine生成了代理對象並把它存儲到cache目錄中,儘管你可能歷來沒有發現過它。記住它這一點很重要。
咱們能夠經過join鏈接來一次性取出product和category數據。這時Doctrine將會返回真正的Category對象,由於不須要延遲加載。

join相關記錄
在以前的咱們的查詢中,會產生兩次查詢操做,一次是獲取原對象,一次是獲取關聯對象。

請記住,你能夠經過網頁調試工具查看請求的全部查詢。
固然,若是你想一次訪問兩個對象,你能夠經過一個join鏈接來避免二次查詢。把下面的方法添加到ProductRepository類中:

// src/AppBundle/Entity/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
    $query = $this->getEntityManager()
        ->createQuery(
            'SELECT p, c FROM AppBundle:Product p
            JOIN p.category c
            WHERE p.id = :id'
        )->setParameter('id', $id);
 
    try {
        return $query->getSingleResult();
    } catch (\Doctrine\ORM\NoResultException $e) {
        return null;
    }
}

如今你就能夠在你的controller中一次性查詢一個產品對象和它關聯的category對象信息了。

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->findOneByIdJoinedToCategory($id);
 
    $category = $product->getCategory();
 
    // ...
}

更多關聯信息
本節中已經介紹了一個普通的實體關聯,一對多關係。對於更高級的關聯和如何使用其餘的關聯(例如 一對一,多對一),請參見 doctrine 的Association Mapping Documentation.

若是你使用註釋,你須要預先在全部註釋加ORM\(如ORM\OneToMany),這些在doctrine官方文檔裏沒有。你還須要聲明use Doctrine\ORM\Mapping as ORM;才能使用annotations的ORM。

配置
Doctrine是高度可配置的,可是你可能永遠不用關心他們。要想了解更多關於Doctrine的配置信息,請查看config reference。

生命週期回調
有時候你可能須要在一個實體被建立,更新或者刪除的先後執行一些操做。這些操做方法處在一個實體不一樣的生命週期階段,因此這些行爲被稱爲」生命週期回調「。

若是你用annotations方式,開啓一個生命週期回調,須要以下設置:(若是你不喜歡你也可使用yaml和xml方式)

/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...
}

如今你能夠告訴Doctrine在任何可用的生命週期事件上來執行一個方法了。好比,假設你想在一個新的實體第一次被建立時設置建立日期列(created)爲當前日期。

// src/AppBundle/Entity/Product.php
 
/**
 * @ORM\PrePersist
 */
public function setCreatedAtValue()
{
    $this->createdAt = new \DateTime();
}

上面的例子假設你已經建立了createdAt屬性(爲在此處顯示)。
如今在實體第一次被保存時,Doctrine會自動調用這個方法使created日期自動設置爲當前日期。

還有一些其餘的生命週期事件,你可使用它。更多生命週期事件和生命週期回調,請查看Doctrine的Lifecycle Events documentation。

生命週期回調和事件監聽
注意到setCreatedValue()方法不須要接收任何參數。這是生命週期回調一般的作法和慣例:生命週期回調應該是簡單方法,更關注於實體內部傳輸數據。好比設置一個建立/更新字段,生成一個定量值等。
若是你須要一些比較大的行爲活動,像執行日誌或者發送郵件,你應該註冊一個擴展類做爲事件監聽器或接收器給它賦予訪問所需資源的權利。想了解更多,請參閱How to Register Event Listeners and Subscribers.
 

Doctrine字段類型參考
Doctrine配備了大量可用的字段類型。它們每個都能映射PHP數據類型到特定的列類型,不管你使用什麼數據庫。對於每個字段類型,Column 均可以被進一步配置,能夠設置length, nullable行爲,name或者其餘配置。想查看更多信息請參閱Doctrine的Mapping Types documentation。

 

總結

有了Doctrine,你能夠集中精力到你的對象以及怎樣把它應用於你的應用程序中,而沒必要擔憂數據庫持久化。由於Doctrine容許你使用任何的PHP對象保存你的數據並依靠映射元數據信息來聯繫一個對象到特定的數據庫表。

儘管Doctrine圍繞着一個簡單的概念發展而來,可是它難以想象的強大。容許你建立複雜的查詢和訂閱事件,經過訂閱事件你能夠在整個持久化過程當中執行一些不一樣的行爲。

相關文章
相關標籤/搜索