Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Как связать Yii Framework и Doctrine 2 ORM?

Anna | 31.05.2014 | нет комментариев

Мне дюже нравится Yii Framework. Он стремительный, комфортный, эластичный. Мне нравится, как реализован в нём паттерн ActiveRecord. Но бывают случаи, когда бизнес-логика, а, если быть точным, доменная логика, дюже трудная и непрерывно растёт и модифицируется. В таких случаях комфортнее пользоваться паттерном DataMapper.

В тоже время мне нравится Doctrine 2 ORM. Это вероятно самая сильная ORM для PHP, имеющая широчайший функционал. Да, допустимо, она «тяжеловата» и замедляет работу приложения. Но начиная разработку, раньше каждого стоит думать об архитектуре приложения, так как «несвоевременная оптимизация корень всех бед»

Таким образом, некогда мне пришла в голову мысль связать 2 этих увлекательных мне инструмента. Как это было сделано, описано ниже.

Установка нужных библиотек

Связать Doctrine и Yii было решено с поддержкой создания соответствующего компонента DoctrineComponent, тот, что бы и предоставлял доступ к функциям Doctrine.

Первым делом, в папке protected фреймворка была сделана папка vendor, куда и был загружен код Doctrine 2 ORM. Установить Doctrine дозволено с поддержкой Composer либо легко скачав/склонировав исходники изGitHub плана Doctrine.
Также, для правильной работы ORM потребуются Doctrine Database Abstraction Layer и Doctrine Common (при установке Doctrine 2 ORM с поддержкой Composer данные зависимости подтягиваются механически).

Помимо того, советую для того, Дабы была вероятность трудиться с Doctrine 2 ORM через консоль установить в туже папку vendor 2 компонента Symfony — это Console (для работы с Doctrine через консоль) и Yaml (при желании изложения сущностей на Yaml)

Таким образом, на данном этапе должна быть получена дальнейшая конструкция плана:

Создание компонента DoctrineComponent

Сейчас дозволено перейти непринужденно к созданию компонента DoctrineComponent. Ниже я приведу целиком код компонента, благо он довольно маленький. Данный код должен находится в папке protected/components в файле DoctrineComponent.php.

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;

class DoctrineComponent extends CComponent
{
    private $em = null;
    private $basePath;
    private $proxyPath;
    private $entityPath;
    private $driver;
    private $user;
    private $password;
    private $host;
    private $dbname;

    public function init()
    {
        $this->initDoctrine();
    }

    public function initDoctrine()
    {
        Yii::setPathOfAlias('Doctrine', $this->getBasePath() . '/vendor/Doctrine');

        $cache = new Doctrine\Common\Cache\FilesystemCache($this->getBasePath() . '/cache');
        $config = new Configuration();
        $config->setMetadataCacheImpl($cache);

        $driverImpl = new AnnotationDriver(new AnnotationReader(), $this->getEntityPath());
        AnnotationRegistry::registerAutoloadNamespace('Doctrine\ORM\Mapping', $this->getBasePath() . '/vendor');

        $config->setMetadataDriverImpl($driverImpl);
        $config->setQueryCacheImpl($cache);
        $config->setProxyDir($this->getProxyPath());
        $config->setProxyNamespace('Proxies');
        $config->setAutoGenerateProxyClasses(true);
        $connectionOptions = array(
            'driver' => $this->getDriver(),
            'user' => $this->getUser(),
            'password' => $this->getPassword(),
            'host' => $this->getHost(),
            'dbname' => $this->getDbname()
        );

        $this->em = EntityManager::create($connectionOptions, $config);
    }

    public function setBasePath($basePath)
    {
        $this->basePath = $basePath;
    }

    public function getBasePath()
    {
        return $this->basePath;
    }

    public function setEntityPath($entityPath)
    {
        $this->entityPath = $entityPath;
    }

    public function getEntityPath()
    {
        return $this->entityPath;
    }

    public function setProxyPath($proxyPath)
    {
        $this->proxyPath = $proxyPath;
    }

    public function getProxyPath()
    {
        return $this->proxyPath;
    }

    public function setDbname($dbname)
    {
        $this->dbname = $dbname;
    }

    public function getDbname()
    {
        return $this->dbname;
    }

    public function setDriver($driver)
    {
        $this->driver = $driver;
    }

    public function getDriver()
    {
        return $this->driver;
    }

    public function setHost($host)
    {
        $this->host = $host;
    }

    public function getHost()
    {
        return $this->host;
    }

    public function setPassword($password)
    {
        $this->password = $password;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function setUser($user)
    {
        $this->user = $user;
    }

    public function getUser()
    {
        return $this->user;
    }

    /**
     * @return EntityManager
     */
    public function getEntityManager()
    {
        return $this->em;
    }
}

Основная часть компонента заключена в способе initDoctrine. Разберём подробнее код.

$cache = new Doctrine\Common\Cache\FilesystemCache($this->getBasePath() . '/cache');
$config = new Configuration();
$config->setMetadataCacheImpl($cache);

Данным кодом мы устанавливаем способ кеширования метаданных сущностей из Doctrine. По-отличному, тип кеширования (в данном случае FilesystemCache) следовало бы отменнее перенести в параметры компонента, тот, что мы могли бы менять при конфигурировании компонента.

$driverImpl = new AnnotationDriver(new AnnotationReader(), $this->getEntityPath());
AnnotationRegistry::registerAutoloadNamespace('Doctrine\ORM\Mapping', $this->getBasePath() . '/vendor');
$config->setMetadataDriverImpl($driverImpl);

С поддержкой кода выше устанавливается драйвер для чтения метаданных сущностей.

 $config->setQueryCacheImpl($cache);
 $config->setProxyDir($this->getProxyPath());
 $config->setProxyNamespace('Proxies');
 $config->setAutoGenerateProxyClasses(true);

Кодом выше мы устанавливаем способ кеширования для запросов (первая строчка), остальные строки — настройка Proxy для Doctrine (путь, пространство имён, установка механического генерирования Proxy-классов)

$connectionOptions = array(
    'driver' => $this->getDriver(),
    'user' => $this->getUser(),
    'password' => $this->getPassword(),
    'host' => $this->getHost(),
    'dbname' => $this->getDbname()
);
$this->em = EntityManager::create($connectionOptions, $config);

Код выше определяет опции соединения с БД. Данные параметры задаются при подключении компонента (будет показано дальше, как подключить компонент).
И в конце создаётся EntityManager с определёнными раннее $connectionOptions и $config, с поддержкой которого и дозволено трудиться с нашими сущностями.

Как подключить DoctrineComponent к плану?

Перейдём к подключению DoctrineComponent к плану.
Сделать этого достаточно легко — нужно легко внести метаморфозы в конфигурационный файл плана (традиционно это main.php)

return array(
    'components' => array(
        'doctrine'=>array(
            'class' => 'DoctrineComponent',
            'basePath' => __DIR__ . '/../',
            'proxyPath' => __DIR__ . '/../proxies',
            'entityPath' => array(
                __DIR__ . '/../entities'
            ),
            'driver' => 'pdo_mysql',
            'user' => 'dbuser',
            'password' => 'dbpassword',
            'host' => 'localhost',
            'dbname' => 'somedb'
        ),
        // ...
);

Сейчас наш компонент будет доступен через Yii::app()->doctrine, а получить EntityManager мы можем через Yii::app()->doctrine->getEntityManager()

Но при таком применении компонента появляется задача в подсказках способов для объекта EntityManager. Для этого было придумано следующее решение:

сlass MainController extends Controller
{
    private $entityManager = null;

    /**
     * @return Doctrine\ORM\EntityManager
     */
    public function getEntityManager()
    {
        if(is_null($this->entityManager)){
            $this->entityManager = Yii::app()->doctrine->getEntityManager();
        }
        return $this->entityManager;
    }

   // ...
}

Всякий контроллер сейчас наследуется от MainController и таким образом, в всяком контроллере дозволено вызвать способ $this->getEntityManager() для приобретения администратора сущностей, причём в IDE сейчас будут трудиться подсказки способов для EntityManager, что бесспорно является плюсом.

Настройка консоли Doctrine

С Doctrine дюже комфортно трудиться через её консоль. Но для этого нужно написать код для её запуска. Данный код приведён ниже. Я положил файл для запуска консоли в папку protected/commands. Дюже отлично также было бы реализовать команду doctrine для ещё больше простого запуска консоли, но мною пока этого сделано не было.

Пример файл doctrine.php для работы с консолью Doctrine.

// change the following paths if necessary
$yii = __DIR__ .'path/to/yii.php';
$config = __DIR__ . 'path/to/config/console.php';

require_once($yii);
Yii::createWebApplication($config);
Yii::setPathOfAlias('Symfony', Yii::getPathOfAlias('application.vendor.Symfony'));

$em = Yii::app()->doctrine->getEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
    'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
    'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

Дабы запустить консоль Doctrine довольно перейти в папку commands и исполнить php doctrine.php.

Валидация моделей и применение моделей в виджете GridView.

Те, кто работал с Doctrine 2 ORM, знают, что реально моделей в общепризнанном их представлении (с способами валидации, приобретения данных из БД, включённой бизнес-логикой и т.д.) нет, а функциональность эта реально разбита на 2 части — Entity и Repository. В Entity традиционно включают бизнес-логику, а в Repository — способы приобретения данных из БД с применение DBAL Doctrine (либо администратор сущностей, либо иным другим методам).

Валидация моделей

Таким образом, на мой взор, разумно было бы включить валидацию данных в класс определенной сущности.
Разглядим на примере сущности User.
Дабы не изобретать велосипед, было решено, что недурно было бы применять теснее встроенную валидацию моделей из Yii, а реально из класса CModel.

Для этого легко-напросто дозволено наследовать сущность User от класса CModel. Пример такой сущности с описанными правилами валидации ниже:

use Doctrine\ORM\Mapping as ORM;

/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="UserRepository")
 * @ORM\HasLifecycleCallbacks
 */
class User extends CModel
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="password", type="string", length=255, nullable=false)
     */
    private $password;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255, nullable=false)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="role", type="string", length=255, nullable=false)
     */
    private $role;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created", type="datetime", nullable=false)
     */
    private $created;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="modified", type="datetime", nullable=false)
     */
    private $modified;

    public function rules(){
        return array(
            array('name, password', 'required'),
            // ...
        );
    }

    public function attributeNames()
    {
        return array(
            'id'=>'id',
            'name'=>'name',
            'email'=>'email',
            'created'=>'created',
            'updated'=>'updated'
        );
    }

    public function attributeLabels()
    {
        return array(
            'description' => 'Description',
            'createdString' => 'Creation Date'
        );
    }

   // ... 
}

Сейчас приведу пример как с этой валидацией трудиться (пример создания нового пользователя ниже):

    /**
     * Creates a new model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     */
    public function actionCreate()
    {
        $user = new User();

        $userData = $this->getRequest()->get('User');
        $course->setAttributes($userData);
        if(!is_null($userData) && $user->validate())
        {
            $user->setName($userData['name']);
            // ... и так дальше все поля

            $this->getEntityManager()->persist($user);
            $this->getEntityManager()->flush();
            $this->redirect(array('view','id'=>$user->getId()));
        }

        $this->render('create',array(
            'model'=>$user,
        ));
    }
Применение моделей в виджете GridView

Одной из самых основных прелестей Yii являются, по-моему, виджеты, а в особенности разные Grid, которые идут в Yii из коробки.
Но исключительный нюанс — они работают с ActiveRecord (я имею ввиду виджет GridView). А лично мне бы хотелось принудить их трудиться с Doctrine и сущностями. Для этого дозволено применять Repository.

При применении GridView есть 2 тесных места — свойства dataProvider и filter. И тут я пою оды разработчикам Yii — для того, Дабы GridView работал с какими-то данными, хорошими от полученных изActiveRecord, довольно, Дабы объект, переданный в GridView в качестве dataProvider верно реализовывал интерфейс IDataProvider (данный интерфейс и следует реализовать в нашем UserRepository), а объект, переданный в filter, — должен наследоваться от CModel (наша сущность User теснее отменно подходит для этого).

Всю реализацию UserRepository приводить не буду, обрисую только всеобщую схему.

use Doctrine\ORM\EntityRepository;

abstract class BaseRepository extends EntityRepository implements IDataProvider
{
    protected $_id;
    private $_data;
    private $_keys;
    private $_totalItemCount;
    private $_sort;
    private $_pagination;

    public $modelClass;
    public $model;
    public $keyAttribute;

    private $_criteria;
    private $_countCriteria;

    public $data;

    abstract protected function fetchData();
    abstract protected function calculateTotalItemCount();

    public function getId(){ //... }

    public function getPagination($className='CPagination'){ //... }

    public function setPagination($value){ //... }

    public function setSort($value){ //... }

    public function getData($refresh=false){ //... }

    public function setData($value){ //... }

    public function getKeys($refresh=false){ //... }

    public function setKeys($value){ //... }

    public function getItemCount($refresh=false){ //... }

    public function getTotalItemCount($refresh=false){ //... }

    public function setTotalItemCount($value){ //... }

    public function getCriteria(){ //... }

    public function setCriteria($value){ //... }

    public function getCountCriteria(){ //... }

    public function setCountCriteria($value){ //... }

    public function getSort($className='CSort'){ //... }

    protected function fetchKeys(){ //... }

    private function _getSort($className){ //... }

}

Выше пример реализации базового репозитория. Реально реализацию многих способов дозволено подсмотреть в Yii классе CActiveDataProvider тот, что и реализует интерфейс IDataProvider. В UserRepository нам придётся определить лишь 2 способа(пример кода ниже):

<?php

class UserRepository extends BaseRepository
{
    protected $_id = 'UserRepository';

    /**
     * Fetches the data from the persistent data storage.
     * @return array list of data items
     */
    protected function fetchData()
    {
       //...
    }

    /**
     * Calculates the total number of data items.
     * @return integer the total number of data items.
     */
    protected function calculateTotalItemCount()
    {
        //...
    }
}

Резюме

Выше я привёл один их методов того, как дозволено трудиться в связке Yii Doctrine 2 ORM. Многие могут сказать, что из-за Doctrine 2 ORM Yii утратит свои превосходства, но не стоит забывать, что Doctrine имеет большое число средств для оптимизации и кеширов

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB