數據庫和Doctrine


  讓我們來面對這個對於任何應用程序來說最爲普遍最具挑戰性的任務,從數據庫中讀取和持久化數據信息。幸運的是,Symfony和Doctrine進行了集成,Doctrine類庫全部目標就是給你一個強大的工具,讓你的工作更加容易。

  Doctrine是完全解耦與Symfony的,所以並不一定要使用它。

  一個簡單例子:一個產品,我們首先來配置數據庫,創建一個Product對象,持久化它到數據庫並把它讀回來。

  首先我們需要創建一個bundle:

$php app/console generate:bundle --namespace=Acme/StoreBundle

 

配置數據庫
  在開始之前,首先需要配置數據庫連接信息。根據慣例,這些信息通常會配置在app/config/parameters.ini 文件中。

複製代碼
;app/config/parameters.ini
[parameters]
    database_driver   = pdo_mysql
    database_host     = localhost
    database_name     = test_project
    database_user     = root
    database_password = password
複製代碼

   將配置信息定義到parameters.ini文件中也是一個常用的做法。定義在該文件中的配置信息將會被主配置文件在安裝Doctrine時引用。

複製代碼
doctrine:
    dbal:
        driver:   %database_driver%
        host:     %database_host%
        dbname:   %database_name%
        user:     %database_user%
        password: %database_password%
複製代碼

  通過把數據庫信息分離到一個特定的文件中,你可以很容易的爲每個服務器保存不同的版本。現在Doctrine知道你的數據庫配置了,你可以用它來創建一個數據庫了。

$php app/console doctrine:database:create

 

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

複製代碼
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;

class Product
{
    protected $name;

    protected $price;

    protected $description;
}
複製代碼

  這樣的類經常被稱爲“Entity",意味着一個基礎類保存數據。它們簡單來滿足你應用程序的業務需要。不過現在它還不能被保存到數據庫中,因爲現在它只不過還是個簡單的PHP類。一旦你學習了Doctrine背後的概念,你可以讓Doctrine來爲你創建實體類。

$php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --fields="name:string(255) price:float description:text"

 


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

  因爲Doctrine能夠做這些,所以你僅僅只需要創建一個meatdata,或者配置告訴DoctrineProduct類和它的屬性應該如何映射到數據庫。這些metadata可以被定義成各種格式,包括YAML,XML或者通過聲明直接定義到Product類中。

  一個bundle只可以接受一種metadata定義格式。比如,不能把YAML定義的metadata和聲明PHP實體類一起混用。

複製代碼
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="product")
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @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;
}
複製代碼

YAML格式metadata定義:

複製代碼
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\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
複製代碼

XML格式metadata定義:

複製代碼
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<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="Acme\StoreBundle\Entity\Product" table="product">
        <id name="id" type="integer" column="id">
            <generator strategy="AUTO" />
        </id>
        <field name="name" column="name" type="string" length="100" />
        <field name="price" column="price" type="decimal" scale="2" />
        <field name="description" column="description" type="text" />
    </entity>
</doctrine-mapping>
複製代碼

表名稱是可選的,可以忽略;如果忽略將會自動的根據entity類名對應。

  如果使用在類中聲明metadata需要首先使用

use Doctrine\ORM\Mapping as ORM;

  導入ORM聲明前綴。然後在每個聲明前使用 ORM\ 比如:

@ORM\Column(...);

  注意:你的類名稱和屬性不能映射到SQL受保護的關鍵字(比如:group 或者 user)。如果你的實體類名是Group,默認情況下你的表面也將是group,這會引起SQL錯誤。當使用另外的類庫或者程序,它們使用了聲明,你應該把@IgnoreAnnotation聲明添加到該類上來告訴Symfony忽略它們。比如我們要阻止@fn 聲明拋出異常,可以這樣:

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

 

生成Getters和Setters
  儘管Doctrine現在知道了如何值就花Product對象到數據庫,但是類本身還是無法使用。因爲Product僅僅是一個標準的PHP類,你需要創建getter和setter方法(比如getName(),setName())來訪問它的屬性(因爲它的屬性是protected),幸運的是Doctrine可以爲我們做這些:

$php app/console doctrine:generate:entites Acme/StoreBundle/Entity/Product

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

關於doctrine:generate:entities命令
        用它你可以生成getters和setters。
        用它在配置@ORM\Entity(repositoryClass="...")聲明的情況下,生成repository類。
        用它可以爲1:n或者n:m生成合適的構造器。
  該命令會保存一個原來Product.php文件的備份Product.php~。 有些時候可也能夠會造成“不能重新聲明類”錯誤,你可以放心的刪除它,來消除錯誤。當然你沒有必要依賴於該命令行,Doctrine不依賴於代碼生成,想標準的PHP類,你只需要保證它的protected/private屬性擁有getter和setter方法即可。你也可以爲一個bundle或者整個實體命名空間內的所有已知實體(任何包含Doctrine映射聲明的PHP類)來生成getter和setter:

$php app/console doctrine:generate:entities AcmeStoreBundle
$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/Acme/StoreBundle/Controller/DefaultController.php
use Acme\StoreBundle\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()->getEntityManager();
    $em->persist($product);
    $em->flush();

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

  事實上,Doctrine瞭解你所有的被管理的實體,當你調用flush()方法時,它會計算出所有的變化,並執行最有效的查詢可能。 比如,你要持久化總是爲100的產品對象,然後調用flush()方法。Doctrine會創建一個唯一的預備語句並重複使用它插入。 這種模式成爲Unit of work。


  在創建和更新對象是,工作流是相同的。Doctrine提供了一個類庫允許你通過編程加載測試數據到你的項目。該類庫爲DoctrineFixturesBundle(http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html)


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

複製代碼
public function showAction($id)
{
       $product = $this->getDoctrine()
                 ->getRepository('AcmeStoreBundle:Product'))
                 ->find($id);
        if(!$product){
             throw $this->createNotFoundException('No product found for id ' .$id);
        }
       //do something,想把$product對象傳遞給一個template等。
}
複製代碼

  當你查詢某個特定的產品是,你總是需要使用它的"respository"。你可以認爲Respository是一個PHP類,它的唯一工作就是幫助你從某個特定類哪裏獲取實體。你可以爲一個實體對象訪問一個repository對象,如下:

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

  其中AcmeStoreBundle:Product是簡潔寫法,你可以在Doctrine中任意使用它來替代實體類的全限定名稱。

Acme\StoreBung\Entity\Product

你一旦有了Repository,你就可以訪問其所有分類的幫助方法了。

複製代碼
//通過主鍵查詢(一般爲"id")
$product=$repository->find($id);

//動態方法名基於列值查找
$product=$repository->findOneById($id);
$product=$repository->findOneByName('foo');

//查詢所有產品
$products=$repository->findAall();
//基於任意列值查找一組產品
$products = $repository->findByPrice(19.99);
複製代碼

你也可以發揮findBy和findOneBy方法的優勢很容易的基於多個條件來獲取對象。

複製代碼
//按照名字和價格來獲取一個匹配的對象
$product=$repository->findOneBy(array('name'=>'foo','price'=>19.99));

//查詢匹配名字的所有產品並按照價格排序
$products = $repository->findBy(
        array('name'=> 'foo'),
        array('price'=>'ASC')
);
複製代碼

 


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

複製代碼
public function updateAction($id)
{
    $em = $this->getDoctrine()->getEntityManager();
    $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);

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

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

    return $this->redirect($this->generateUrl('homepage'));
}
複製代碼

更新一個對象包括三步:

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

  注意調用 $em->persist($product) 在這裏沒有必要。我們回想一下,調用該方法的目的主要是告訴Doctrine來管理或者“watch"$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的查詢創建器。

用DQL查詢對象:
  假設你想查詢產品,需要返回價格高於19.99的產品,並且要求按價格從低到高排列。我們可以在相應的Controller裏進行如下操作:

$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery(
    'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC'
)->setParameter('price', '19.99');

$products = $query->getResult();

  如果你習慣了寫SQL,那麼對於DQL也應該不會感到陌生。它們之間最大的不同就是你需要思考對象,而不是數據庫錶行。正因爲如此,所以你從AcmeStoreBundle:Product選擇並給它定義別名p。getResult()方法返回一個結果數組。如果你只需要一個對象,你可以使用getSingleResult()方法。

$product = $query->getSingleResult();

  如果沒有符合要求的結果,getSingleResult()方法會拋出一個 Doctrine\ORM\NoResultException 異常和如果不只有一個結果返回那麼就會
拋出一個Doctrine\ORM\NonUniqueResultException 異常。所以,如果你要使用該方法的話,需要把它包裹在一個try-catch塊內,以確保只有
一個結果被返回。

複製代碼
$query = $em->createQuery('SELECT ....')
    ->setMaxResults(1);

try {
    $product = $query->getSingleResult();
} catch (\Doctrine\Orm\NoResultException $e) {
    $product = null;
}
// ...
複製代碼

DQL的語法難以置信的強大,允許你很容易的倆和多個實體,分組等進行查詢。

 

設置參數:
  注意setParameter()方法,在使用Doctrine時,把任何的外部值設置成佔位符是一個非常好的做法。 
比如上例中 ...WHERE p.price>:price ...
這樣你可以通過調用setParameter()方法爲price佔位符設置具體值。
->setParameter('price', '19.99')


  使用參數而不是直接把具體在插入到查詢字符串中是爲了放置SQL注入攻擊,所以必須始終這麼做。如果你使用了多個參數,你可以使用setParameters()方法一次性設置他們的值。

->setParameters(array(
    'price'=>'19.99',
    'name' =>'foo',
))

 


使用Doctrine的查詢創建器
  除了直接編寫查詢以外,你可以使用Doctrine的QueryBuilder來做相同的工作。面前對象接口。如果你使用IDE,你還可以獲取自動編譯檢查的好處。

複製代碼
$repository = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product');

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

$products = $query->getResult();
複製代碼

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

 

自定義Repository類
  在上面你已經開始在controller中創建和使用負責的查詢了。爲了隔離,比阿育測試和重用這些查詢,一個好的辦法是爲你的實體創建一個自定義的repository類並添加相關邏輯查詢方法。要定義repository類,首先需要在你的映射定義中添加repository類的聲明:

在實體類中聲明方式:

複製代碼
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="Acme\StoreBundle\Repository\ProductRepository")
 */
class Product
{
    //...
}
複製代碼

YAML格式:

# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
    type: entity
    repositoryClass: Acme\StoreBundle\Repository\ProductRepository
    # ...

XML格式:

複製代碼
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<!-- ... -->
<doctrine-mapping>

    <entity name="Acme\StoreBundle\Entity\Product"
            repository-class="Acme\StoreBundle\Repository\ProductRepository">
            <!-- ... -->
    </entity>
</doctrine-mapping>
複製代碼

  然後通過運行跟之前生成丟失的getter和setter方法同樣的命令行,Doctrine會爲你自動生成repository類。

$php app/console doctrine:generate:entities Acme

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

複製代碼
// src/Acme/StoreBundle/Repository/ProductRepository.php
namespace Acme\StoreBundle\Repository;

use Doctrine\ORM\EntityRepository;

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

  注意在Repository類中可以通過$this->getEntityManager()方法類獲取實體管理者。如此一來你就可以像使用默認的方法一樣使用這個新定義的方法了:

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

  在使用自定義的repository類時,你依然可以訪問原有的默認查找方法,比如find() 和findAll()等。


實體關係/關聯
  假設你應用程序中的產品屬於一確定的分類。這時你需要一個分類對象和一種把Product和Category對象聯繫在一起的方式。首先我們創建Category實體,我們最終要通過Doctrine來對其進行持久化,所以我們這裏讓Doctrine來幫我們創建這個類。

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

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


關係映射元數據:
  聯繫Category和Product兩個實體,首先在Category類中創建一個products屬性:
Category類中聲明格式:

複製代碼
// src/Acme/StoreBundle/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();
    }
}
複製代碼

YAML定義格式:

複製代碼
# src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml
Acme\StoreBundle\Entity\Category:
    type: entity
    # ...
    oneToMany:
        products:
            targetEntity: Product
            mappedBy: category
    # 不要忘記在實體的 __construct() 方法中初始化集合
複製代碼

  首先,因爲一個Category對象將關係到多個Product對象,一個products數組屬性被添加到Category類保存Product對象。

  其次,這裏沒有被做因爲Doctrine需要它,但在應用程序中爲每一個Category來保存一個Product數組非常有用。

  代碼中__construct()方法非常重要,因爲Doctrine需要$products熟悉成爲一個ArrayCollection對象,它跟數組非常類似。targetEntity 的值可以使用合法的命名空間引用任何實體,而不僅僅是定義在同一個類中的實體。 如果要關係一個定義在不同的類或者bundle中的實體則需要輸入完全的命名空間作爲目標實體。


  接下來,因爲每個Product類可以關聯一個Category對象,所有添加一個$category屬性到Product類:
在Product類聲明中定義:

複製代碼
// src/Acme/StoreBundle/Entity/Product.php
// ...

class Product
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;
}
複製代碼

YAML定義格式:

複製代碼
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
    type: entity
    # ...
    manyToOne:
        category:
            targetEntity: Category
            inversedBy: products
            joinColumn:
                name: category_id
                referencedColumnName: id
複製代碼

  最後,到現在爲止,我們添加了兩個新屬性到Category和Product類。現在告訴Doctrine來爲它們生成getter和setter方法。

$php app/console doctrine:generate:entities Acme

  我們先不看Doctrine的元數據,你現在有兩個類Category和Product,並且擁有一個一對多的關係。Category包含一個數組Product對象,Product包含一個Category對象。換句話說,你已經創建了你所需要的類了。

  現在讓我們來看看在Product類中爲$category配置的元數據。它告訴Doctrine關係類是Category並且它需要保存category的id到product表的category_id字段。換句話說,相關的分類對象將會被保存到$category屬性中,但是在底層,Doctrine會通過存儲category的id值到product表的category_id列持久化它們的關係。Category類中$product屬性的元數據配置不是特別重要,它僅僅是告訴Doctrine去查找Product.category屬性來計算出關係映射是什麼。

  在繼續下去之前,首先確定告訴Doctrine添加一個新的category表和product.category_id列以及新的外鍵。

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

 


保存關係實體:
  現在讓我們來看看Controller內的代碼如何處理:

複製代碼
// ...
use Acme\StoreBundle\Entity\Category;
use Acme\StoreBundle\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);
        // relate this product to the category
        $product->setCategory($category);

        $em = $this->getDoctrine()->getEntityManager();
        $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('AcmeStoreBundle:Product')
        ->find($id);

    $categoryName = $product->getCategory()->getName();

    // ...
}
複製代碼

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

  重要的是你很容易的訪問到了product的關聯對象category。但是category的數據並不會被取出來而直到你請求category的時候。這就是延遲加載。你也可以從其它方向進行查詢:

複製代碼
public function showProductAction($id)
{
    $category = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Category')
        ->find($id);

    $products = $category->getProducts();

    // ...
}
複製代碼

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


關係和代理類:
  延遲加載成爲可能是因爲Doctrine返回一個代理對象來代替真正的對象:

複製代碼
$product = $this->getDoctrine()
     ->getRepository('AcmeStoreBundle:Product')
     ->find($id);

$category = $product->getCategory();

// 輸出結果 "Proxies\AcmeStoreBundleEntityCategoryProxy"
echo get_class($category);
複製代碼

  該代理對象繼承了Category對象,從外表到行爲都非常像category對象。通過這個代理對象,Doctrine可以延遲查詢真正的Category對象數據,直到真正需要它時(調用$category->getName())。Doctrine生成了代理對象並把它存儲到cache目錄中,儘管你可能從來沒有發現過它。記住它這一點很重要。

  我們可以通過join連接來一次性取出product和category數據。這時Doctrine將會返回真正的Category對象,因爲不需要延遲加載。


連接相關記錄:
  在之前的我們的查詢中,會產生兩次查詢操作,一次是獲取原對象,一次是獲取關聯對象。當然,如果你想一次訪問兩個對象,你可以通過一個join連接來避免二次查詢。把下面的方法添加到ProductRepository類中:

複製代碼
// src/Acme/StoreBundle/Repository/ProductRepository.php

public function findOneByIdJoinedToCategory($id)
{
    $query = $this->getEntityManager()
        ->createQuery('
            SELECT p, c FROM AcmeStoreBundle: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('AcmeStoreBundle:Product')
        ->findOneByIdJoinedToCategory($id);

    $category = $product->getCategory();

    // ...
}
複製代碼

 

 

生命週期回調:
  有時候你可能需要在一個實體被創建,更新或者刪除的前後執行一些行爲。因爲它們回調的方法處在一個實體不同的生命週期階段,所以這些行爲被稱爲"生命週期回調“。如果你用聲明元數據方式,開啓一個生命週期回調,需要如下設置:

複製代碼
/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...
}
複製代碼

  如果你選擇YAML或者XML格式爲你定義映射,則不需要它。現在你可以告訴Doctrine在任何可用的生命週期事件上來執行一個方法了。比如,假設你想在一個新的實體第一次被創建時設置設置創建日期列(created)爲當前日期。

聲明式定義:

複製代碼
/**
* @ORM\PrePersist
*/
public function setCreatedValue()
{
       $this->created = new \DateTime();
}
複製代碼

YAML格式定義:

# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
    type: entity
    # ...
    lifecycleCallbacks:
        prePersist: [ setCreatedValue ]

XML格式定義:

複製代碼
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml -->
<!-- ... -->
<doctrine-mapping>

    <entity name="Acme\StoreBundle\Entity\Product">
            <!-- ... -->
            <lifecycle-callbacks>
                <lifecycle-callback type="prePersist" method="setCreatedValue" />
            </lifecycle-callbacks>
    </entity>
</doctrine-mapping>
複製代碼

  現在在實體第一次被保存時,Doctrine會自動調用這個方法使created日期自動設置爲當前日期。還有其它生命週期事件可用:

preRemove
postRemove
prePersist
postPersist
preUpdate
postUpdate
postLoad
loadClassMetadata

 

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

 

Doctrine擴展:Timestampable, Sluggable
  Doctrine非常靈活,許多第三方擴展可以使用,讓你很容易在你的實體上執行一些重複和通用的任務。包括Sluggable,Timestampable,Loggable,Translatable 和 Tree。


Doctrine字段類型參考:
  Doctrine配備了大量可用的字段類型。它們每一個都能映射PHP數據類型到特定的列類型,無論你使用什麼數據庫。下面是Doctrine支持的數據類型:

字符串:
         string 短字符串
         text 大型字符串
數字:
         integer
         smallint
         bigint
         decimal
         float
日期和時間:
         date
         time
         datetime
其它類型:
         boolean
         object(序列化並存儲到CLOB字段)
         array(序列化並存儲到CLOB字段)


可選字段:
  每個字段都有一些可選項。包括type(默認string),name,length,unique 和nullable。
比如:

複製代碼
/**
 *字符串字段長度爲255 不能爲空
 * (影響默認值的 "type", "length" 和 *nullable* 可選)
 *
 * @ORM\Column()
 */
protected $name;

/**
 * 字符串字段長度 150保存到 "email_address" 列
 * 並且有一個唯一索引.
 *
 * @ORM\Column(name="email_address", unique=true, length=150)
 */
protected $email;
複製代碼

YAML格式:

複製代碼
fields:
    #字符串長度爲 255 不能爲空l
    # (影響默認值的 "length" 和 *nullable* 可選)
    # type 屬性在yml中是必須定義的
    name:
        type: string

    # 字符串長度爲150持久化一個 "email_address" 列
    # 並有一個唯一索引.
    email:
        type: string
        column: email_address
        length: 150
        unique: true
複製代碼

 


控制檯命令:
  Doctrine2 ORM繼承官方的多個控制檯命令在doctrine命名空間下。你可以通過如下命令查看:

$php app/console

  一個可用的命令行列表將會被打印出來。有許多是以doctrine:開頭的。你可通過運行help命令來查看它們的詳細,比如,查看doctrine:database:create 任務,則需要運行:

$php app/console help doctrine:database:create

另外一些可用的有趣的命令行任務包括:

  doctrine:ensure-production-settings 用來查看當前還將配置是否對產品有效。這個一般在prod環境下運行:
  

$php app/console doctrine:ensure-production-settings --env=prod

 

  doctrine:mapping:import 允許Doctrine自己檢查一個已有的數據庫並創建映射信息。

  doctrine:query:dql 和 doctrine:query:sql 允許你直接在命令行執行DQL或者SQL查詢。


總結思考:
  有了Doctrine,你可以集中精力到你的對象以及怎樣把它應用於你的應用程序中,而不必擔心數據庫持久化。因爲Doctrine允許你使用任何的PHP對象保存你的數據並依靠映射元數據信息來聯繫一個對象到特定的數據庫表。

  儘管Doctrine圍繞着一個簡單的概念發展而來,但是它不可思議的強大。允許你創建複雜的查詢和訂閱事件,通過訂閱事件你可以在整個持久化過程中執行一些不同的行爲。

 

參考URL:http://symfony.com/doc/current/book/doctrine.html

發佈了31 篇原創文章 · 獲贊 7 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章