之前和最近一個項目用到了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安裝
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;
返回了總條數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可以設置哪些屬性:
參考文章:
有哪些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來進行查看命令
看看這些命令,大致可以完成的功能是:
數據庫schema生成php的Entity代碼
php的Entity代碼生成數據庫schema
緩存相關操作
數據庫schema相關操作
but對於這些命令:
但是自己試過才知道,有些還是有一些限制的的,比如它必須要求表必須有一個自增主鍵,而且並且是庫中的所有表都有這個要求。。。才能生成ORM的Entity等數據。
還有生成entity也必須要先創建一個基本的模板之類的。
但是這些工具總的來說還是很有用的,聊勝於無。
總結
doctrine就是一個很龐大的ORM系統,它可以嵌入到其他框架中,比如symfony,比如Yii等。
ORM的最終目的就是將邏輯層和持久層分離,在這個層面來說,doctrine很好地完成了這個任務。
doctrine已經將你能考慮到的操作都進行封裝好了,相信如果熟悉了之后,開發過程應該是會非常快的