doctrine2到底是個什么玩意


之前和最近一個項目用到了Doctrine,由於是別人搭建的,自己沒有很了解,最近又開始做的時候發現拙荊見肘,於是看了一下doctrine教程,本文就是加上自己理解的doctrine教程文檔筆記了。

Doctrine2 配置需求

需要php5.3.3及以上

可以使用composer安裝

什么是Doctrine?

Doctrine是一個ORM(Object-relational mapper),提供php數據庫和PHP對象的映射。他和其他的ORM一樣都是為了保證持久層和邏輯層的分類而存在的。

什么是Entity

Entity是PHP的一個對象

Entity對應的表需要有主鍵

Entity中不能含有final屬性或者final方法

教程:

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html

教程代碼的github地址在:

https://github.com/doctrine/doctrine2-orm-tutorial

使用composer安裝

Image(17)[4]

Image(18)[4]

doctrine是可以根據Entity代碼來生成數據表的

在src文件夾($config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);)

中寫出Entity代碼

然后使用

php vendor/bin/doctrine orm:schema-tool:create

工具來生成數據表

用下面的命令更新數據表

$ php vendor/bin/doctrine orm:schema-tool:update --force --dump-sql

當然在bootstrap中需要設置連接mysql數據庫

// database configuration parameters
$conn = array(
    'driver' => 'pdo_mysql',
    'host' => 'localhost',
    'user' => 'yjf',
    'password' => 'yjf',
    'dbname' => 'yjf',
    'path' => __DIR__ . '/db.sql',
);

// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);

其中Entity除了可以使用代碼來進行設置外,也可以使用xml和yml文件進行設置。

它的命令行讀取的是cli-config.php這個配置數據,所以在使用doctrine的命令行之前,需要先編寫這個配置文件。

使用EntityManager能對Entity進行增刪改查的操作

增加:
$product = new Product();
$product->setName($newProductName);

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

查詢:
$entityManager->find('Product', $id)

更新:
$product = $entityManager->find('Product', $id);
$product->setName($newName);
$entityManager->flush();

刪除:
$product = $entityManager->find('Product', $id);
$product->remove();
$entityManager->flush();

如何調試

doctrine由於EntityManager結構復雜,所以使用var_dump()返回的數據及其龐大,並且可讀性差。應該使用

Doctrine\Common\Util\Debug::dump()來打印信息。

表之間的關聯如何體現在Entity上

首先明確表和表的關聯有幾種:

一對一

一對多

多對一

多對多

比如教程中舉的例子,bug系統

bug表和user表分別存儲bug信息和user信息

每個bug有個工程師engineer的屬性,代表這個bug是由哪個工程師開發的,那么就有可能有多個bug是由一個工程師開發的。所以bug表對於user表就是多對一的關系。user表對於bug表就是一對多的關系。

bug表和product表分別存儲bug信息和出bug的產品信息

一個bug可能有多個產品一起出現問題導致的,而一個產品也有可能有多個bug,所以bug表和product表就是多對多的關系。

一對一的關系就比較簡單了。

一對多如何設置

這里主要說下一對多和多對一的時候,在bug的Entity中和user的Entity中應該對應這樣設置:

#bug.php
    /**
     * @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
     **/
    protected $engineer;

#user.php
    /**
     * @OneToMany(targetEntity="Bug", mappedBy="engineer")
     * @var Bug[]
     **/
    protected $assignedBugs = null;

這里ManyToOne和OneToMany是互相對應的,inversedBy和mappedBy也是互相對應的。

這樣如果使用doctrine自動生成表結構:

mysql> show create table users;

| users | CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |


mysql> show create table bugs;

| bugs  | CREATE TABLE `bugs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `engineer_id` int(11) DEFAULT NULL,
  `reporter_id` int(11) DEFAULT NULL,
  `description` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `created` datetime NOT NULL,
  `status` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_1E197C9F8D8CDF1` (`engineer_id`),
  KEY `IDX_1E197C9E1CFE6F5` (`reporter_id`),
  CONSTRAINT `FK_1E197C9E1CFE6F5` FOREIGN KEY (`reporter_id`) REFERENCES `users` (`id`),
  CONSTRAINT `FK_1E197C9F8D8CDF1` FOREIGN KEY (`engineer_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |

可以看出的是bugs生成了engineer_id屬性,然后自動生成外鍵的索引。

更多的entity和mysql的對應關系是:

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

如何使用DQL進行查詢

對,你沒有看錯,這里是DQL而不是SQL。DQL是毛語言呢?Document Query Language,意思就是文檔化的sql語句,為什么sql語句需要文檔化呢?sql語句更傾向於表結構實現,所以在寫sql語句的時候頭腦中需要具現化的是表結構,而ORM的目的就是不需要開發者關注表結構,所以需要一個不基於表結構的查詢語句,又能直接翻譯成為SQL語句,這就是DQL。DQL可以直接對Entity進行增刪改查,而不需要直接對表進行操作。

比如下面的一個例子:

$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";

$query = $entityManager->createQuery($dql);
$query->setMaxResults(30); 
$bugs=$query->getResult();

看這里的sql中From的就是“Bug”,這個是Entity的類,其實熟悉了sql,dql的查詢語法也是一模一樣的。

考慮使用dql而不是sql除了和ORM目標一致外,還有一個好處,就是存儲層和邏輯層的耦合分開了。比如我的存儲層要從mysql換成mongodb,那么邏輯層是什么都不需要動的。

使用Repository

到這里就嘀咕,我經常進行的查詢是根據字段查詢列表啥的,難道每次需要我都拼接dql么?當然不用,doctrine為我們准備了repository的概念,就是查詢庫,里面封裝了各種查詢常用的方法。

使用如下:

<?php
$product = $entityManager->getRepository('Product')
                         ->findOneBy(array('name' => $productName));

Repository對應的操作有:

find()
findAll()
findBy()
findOneBy()
findOneByXXX()

示例:

<?php// $em instanceof EntityManager
$user = $em->getRepository('MyProject\Domain\User')->find($id);

<?php// $em instanceof EntityManager

// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));

// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));

// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));


<?php// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));

// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');

 

doctrine還允許自己創建Repository,然后只需要在Entity中說明下repositoryClass就可以了。

class UserRepository extends EntityRepository
{
    public function getAllAdminUsers()
    {
        return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
                         ->getResult();
    }}

如何使用原生的sql語句來做查詢?

除了dql之外,doctrine也允許使用原生的sql語句來做查詢。這篇教程有最詳細的說明http://docs.doctrine-project.org/en/latest/reference/native-sql.html

主要是提供了createNativeQuery的方法

<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns an association to an Address but the Address is not loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');

$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');

$users = $query->getResult();

 

這里唯一讓人不解的就是ResultSetMapping了,ResultSetMapping也是理解原生sql查詢的關鍵。

其實也沒什么不解的了,ORM是不允許數據庫操作返回的不是Object的,所以ResultSetMapping就是數據庫數據和Object的結構映射。

這個Mapping也可以在Entity中進行設置。

如何使用QueryBuilder

QueryBuilder是doctrine提供的一種在DQL之上的一層查詢操作,它封裝了一些api,提供給用戶進行組裝DQL的。

QueryBuilder的好處就是看起來不用自己字符串拼裝查詢語句了。

關於QueryBuilder的詳細說明可以看這篇文章:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html

<?php
// $qb instanceof QueryBuilder
$qb = $em->createQueryBuilder();

$qb->select('u')
   ->from('User', 'u')
   ->where('u.id = ?1')
   ->orderBy('u.name', 'ASC');
 

QueryBuilder的接口有:

<?phpclass QueryBuilder{
    // Example - $qb->select('u')
    // Example - $qb->select(array('u', 'p'))
    // Example - $qb->select($qb->expr()->select('u', 'p'))
    public function select($select = null);

    // Example - $qb->delete('User', 'u')
    public function delete($delete = null, $alias = null);

    // Example - $qb->update('Group', 'g')
    public function update($update = null, $alias = null);

    // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
    // Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
    // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
    public function set($key, $value);

    // Example - $qb->from('Phonenumber', 'p')
    public function from($from, $alias = null);

    // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
    // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1')
    public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);

    // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
    // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
    public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);

    // NOTE: ->where() overrides all previously set conditions
    //
    // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
    // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
    // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
    public function where($where);

    // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
    public function andWhere($where);

    // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
    public function orWhere($where);

    // NOTE: -> groupBy() overrides all previously set grouping conditions
    //
    // Example - $qb->groupBy('u.id')
    public function groupBy($groupBy);

    // Example - $qb->addGroupBy('g.name')
    public function addGroupBy($groupBy);

    // NOTE: -> having() overrides all previously set having conditions
    //
    // Example - $qb->having('u.salary >= ?1')
    // Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
    public function having($having);

    // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
    public function andHaving($having);

    // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
    public function orHaving($having);

    // NOTE: -> orderBy() overrides all previously set ordering conditions
    //
    // Example - $qb->orderBy('u.surname', 'DESC')
    public function orderBy($sort, $order = null);

    // Example - $qb->addOrderBy('u.firstName')
    public function addOrderBy($sort, $order = null); // Default $order = 'ASC'}

更多更復雜的查詢可以查看上文的鏈接。

查詢結果是只能返回對象嗎?

當然不只,當你執行query的時候可以試試使用:

$result = $query->getResult();
$single = $query->getSingleResult();
$array = $query->getArrayResult();
$scalar = $query->getScalarResult();
$singleScalar = $query->getSingleScalarResult();

doctrine查詢操作總結

現在總結下,doctrine2 做查詢操作有下面幾種方法

1 使用EntityManager直接進行find查詢

2 使用DQL進行createQuery($dql)進行查詢

3 使用QueryBuilder進行拼裝dql查詢

4 使用Repository進行查詢

5 使用原生的sql進行createNativeQuery($sql)進行查詢

doctrine2的增加,刪除,更新操作都需要使用Entity進行操作

一個項目有幾個實現路徑:

1 Code First:先用代碼寫好Object,然后根據Object生成數據庫

2 Model First:先用工具寫好UML,然后根據UML生成數據庫和PHP代碼

3 Database First:先寫好數據庫的schema表,然后生成PHP代碼

如何做分頁操作

分頁操作是經常使用到的,doctrine使用了Paginator類來做這個操作

比如:

<?php
// list_bugs_array.php
use Doctrine\ORM\Tools\Pagination\Paginator;
require_once "bootstrap.php";

$dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
       "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
$query = $entityManager->createQuery($dql)
               ->setFirstResult(0)
               ->setMaxResults(1);

$paginator = new Paginator($query, $fetchJoinCollection = true);

$c = count($paginator);
echo "count: $c" . "\r\n";

$bugs = $query->getArrayResult();

foreach ($bugs as $item) {
     print_r($item);
}
exit;

Image(16)[4]

返回了總條數2,也返回了查詢的結果。贊~!

如何進行sql和dql的調試

我們不免調試的時候要取出sql和dql語句。我們可以使用

$query->getDQL()
$query->getSQL()

來獲取出實際進行查詢的sql語句

為什么在增刪更新的時候有個flush操作

doctrine在增加,刪除,更新的時候並不是直接進行操作,而是將操作存放在每個EntityManager的UnitOfWork。

你可以使用

$entityManager->getUnitOfWork()
$entityManager->getUnitOfWork()->size()
$entityManager->getEntityState($entity)
來控制UnitOfWork
 

如何注入Entity增加,刪除,更新操作

doctrine提供了監聽Event的功能,比如你要在Persist之前做一個日志處理,你就可以實現一個Listener,其中實現了prePersist方法
然后把Listener掛載到Entity上

<?php
namespace MyProject\Entity;

/** @Entity @EntityListeners({"UserListener"}) */
class User
{
    // ....
}

如何實現事務?

<?php
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
    //... do some work
    $user = new User;
    $user->setName('George');
    $em->persist($user);
    $em->flush();
    $em->getConnection()->commit();
} catch (Exception $e) {
    $em->getConnection()->rollback();
    $em->close();
    throw $e;
}

使用DQL只能進行查詢操作嗎?

當然不只,我們可以使用execute()來對增刪改查的DQL語句進行操作

<?php
$q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000');
$numDeleted = $q->execute();

Entity可以設置哪些屬性:

參考文章:

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html

有哪些Cache機制

doctrine可以支持APC,Memcache,Xcache,Redis這幾種緩存機制

所有這些緩存機制都是基於一個抽象方法,這個抽象方法中有的接口有:

fetch($id)
contains($id)
save($id, $data, $lifeTime=false)
delete($id)

各自對應的初始化代碼:

APC:
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');

MemCache:
<?php
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);

$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');

Xcache:
<?php
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');

Redis:
<?php
$redis = new Redis();
$redis->connect('redis_host', 6379);

$cacheDriver = new \Doctrine\Common\Cache\RedisCache();
$cacheDriver->setRedis($redis);
$cacheDriver->save('cache_id', 'my_data');

還有一個命令clear-cache可以用來進行緩存的增刪改查

具體參考文章:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/caching.html

doctrine提供的工具有哪些

參考文章:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html

也可以使用list來進行查看命令

Image(19)[4]

看看這些命令,大致可以完成的功能是:

數據庫schema生成php的Entity代碼

php的Entity代碼生成數據庫schema

緩存相關操作

數據庫schema相關操作

but對於這些命令:

但是自己試過才知道,有些還是有一些限制的的,比如它必須要求表必須有一個自增主鍵,而且並且是庫中的所有表都有這個要求。。。才能生成ORM的Entity等數據。

還有生成entity也必須要先創建一個基本的模板之類的。

但是這些工具總的來說還是很有用的,聊勝於無。

總結

doctrine就是一個很龐大的ORM系統,它可以嵌入到其他框架中,比如symfony,比如Yii等。

ORM的最終目的就是將邏輯層和持久層分離,在這個層面來說,doctrine很好地完成了這個任務。

doctrine已經將你能考慮到的操作都進行封裝好了,相信如果熟悉了之后,開發過程應該是會非常快的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM