• doctrine2到底是个什么玩意


    本文非原创,转载自:http://www.cnblogs.com/yjf512/p/3375614.html

    感谢原作者分享!

    之前和最近一个项目用到了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代码

    然后使用

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

    工具来生成数据表

    用下面的命令更新数据表

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

    当然在bootstrap中需要设置连接mysql数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 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进行增删改查的操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    增加:
    $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()返回的数据及其庞大,并且可读性差。应该使用

    DoctrineCommonUtilDebug::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中应该对应这样设置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #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自动生成表结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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对应的操作有:

    1
    2
    3
    4
    5
    find()
    findAll()
    findBy()
    findOneBy()
    findOneByXXX()

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php// $em instanceof EntityManager
    $user = $em->getRepository('MyProjectDomainUser')->find($id);
     
    <?php// $em instanceof EntityManager
     
    // All users that are 20 years old
    $users = $em->getRepository('MyProjectDomainUser')->findBy(array('age' => 20));
     
    // All users that are 20 years old and have a surname of 'Miller'
    $users = $em->getRepository('MyProjectDomainUser')->findBy(array('age' => 20, 'surname' => 'Miller'));
     
    // A single user by its nickname
    $user = $em->getRepository('MyProjectDomainUser')->findOneBy(array('nickname' => 'romanb'));
     
     
    <?php// A single user by its nickname
    $user = $em->getRepository('MyProjectDomainUser')->findOneBy(array('nickname' => 'romanb'));
     
    // A single user by its nickname (__call magic)
    $user = $em->getRepository('MyProjectDomainUser')->findOneByNickname('romanb');

    doctrine还允许自己创建Repository,然后只需要在Entity中说明下repositoryClass就可以了。

    1
    2
    3
    4
    5
    6
    7
    class UserRepository extends EntityRepository
    {
        public function getAllAdminUsers()
        {
            return $this->_em->createQuery('SELECT u FROM MyDomainModelUser u WHERE u.status = "admin"')
                             ->getResult();
        }}

    如何使用原生的sql语句来做查询?

    除了dql之外,doctrine也允许使用原生的sql语句来做查询。这篇教程有最详细的说明http://docs.doctrine-project.org/en/latest/reference/native-sql.html

    主要是提供了createNativeQuery的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    // $qb instanceof QueryBuilder
    $qb = $em->createQueryBuilder();
     
    $qb->select('u')
       ->from('User', 'u')
       ->where('u.id = ?1')
       ->orderBy('u.name', 'ASC');
     

    QueryBuilder的接口有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    <?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', ExprJoin::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', ExprJoin::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的时候可以试试使用:

    1
    2
    3
    4
    5
    $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类来做这个操作

    比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php
    // list_bugs_array.php
    use DoctrineORMToolsPaginationPaginator;
    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" . " ";
     
    $bugs = $query->getArrayResult();
     
    foreach ($bugs as $item) {
         print_r($item);
    }
    exit;

    Image(16)[4]

    返回了总条数2,也返回了查询的结果。赞~!

    如何进行sql和dql的调试

    我们不免调试的时候要取出sql和dql语句。我们可以使用

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

    来获取出实际进行查询的sql语句

    为什么在增删更新的时候有个flush操作

    doctrine在增加,删除,更新的时候并不是直接进行操作,而是将操作存放在每个EntityManager的UnitOfWork。

    你可以使用

    1
    2
    3
    4
    $entityManager->getUnitOfWork()
    $entityManager->getUnitOfWork()->size()
    $entityManager->getEntityState($entity)
    来控制UnitOfWork
    1
      

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

    doctrine提供了监听Event的功能,比如你要在Persist之前做一个日志处理,你就可以实现一个Listener,其中实现了prePersist方法
    然后把Listener挂载到Entity上

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    namespace MyProjectEntity;
     
    /** @Entity @EntityListeners({"UserListener"}) */
    class User
    {
        // ....
    }

    如何实现事务?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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语句进行操作

    1
    2
    3
    <?php
    $q = $em->createQuery('delete from MyProjectModelManager 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这几种缓存机制

    所有这些缓存机制都是基于一个抽象方法,这个抽象方法中有的接口有:

    1
    2
    3
    4
    fetch($id)
    contains($id)
    save($id, $data, $lifeTime=false)
    delete($id)

    各自对应的初始化代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    APC:
    <?php
    $cacheDriver = new DoctrineCommonCacheApcCache();
    $cacheDriver->save('cache_id', 'my_data');
     
    MemCache:
    <?php
    $memcache = new Memcache();
    $memcache->connect('memcache_host', 11211);
     
    $cacheDriver = new DoctrineCommonCacheMemcacheCache();
    $cacheDriver->setMemcache($memcache);
    $cacheDriver->save('cache_id', 'my_data');
     
    Xcache:
    <?php
    $cacheDriver = new DoctrineCommonCacheXcacheCache();
    $cacheDriver->save('cache_id', 'my_data');
     
    Redis:
    <?php
    $redis = new Redis();
    $redis->connect('redis_host', 6379);
     
    $cacheDriver = new DoctrineCommonCacheRedisCache();
    $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已经将你能考虑到的操作都进行封装好了,相信如果熟悉了之后,开发过程应该是会非常快的

  • 相关阅读:
    Product of Array Except Self
    Sliding Window Maximum
    First Bad Version
    Perfect Squares
    IPsec Note
    BGP实验第9-10选路原则
    BGP选路第3条原则
    BGP选路原则第1条至第8条
    BGP选路原则笔记 Cyrus
    Lab Block hole of BGP
  • 原文地址:https://www.cnblogs.com/walter371/p/4664770.html
Copyright © 2020-2023  润新知