В виду того, что проект Kohana вообще закрылся и версию 3.4 c namespace мы наверное никогда не увидим следует вывод – не стоит начинать на ней новые проекты. Но что делать, если приходится поддерживать старые. Не пользоваться же ORM Kohana, ее знание и знание ее особенностей уже ни кому не нужны. Значит надо подключить Doctrine.
Я столкнулся с трудностями при решении такой простой задачи.
Существующие в не большом количестве модули не работают корректно или имеют баги. Так же я использую версию Kohana 3.2, что сразу же отсеивает половину модулей. Писать свое решение или делать downgrade с 3.3 версии было бы дольше. Все же я просмотрел все модули и нашел приемлемый.
Это был модуль Qballinternet/kohana-doctrine, который он форкнул от synchrone/kohana-doctrine, который тот форкнул от gimpe/kohana-doctrine, который и является первоначальным автором. Дело было 4 года назад.. А последний маинтайнер Qballinternet похоже тоже пропал (на связь не выходит, pull request не принимает).
Я взял версию с последнего форка и начал подключать и дорабатывать.
Модуль Doctrine
Адрес seyfer/kohana-doctrine.
Самый главный минус этого модуля до переделки был в том, что не используется composer и папка vendor с Doctrine просто закомичена с модулем вместе. Я решил избавиться сначала от лишнего и перейти на composer. Для этого я форкнул Qballinternet/kohana-doctrine, написал composer.json, удалил vendor который был в кеше git.
Дело еще в том, что в репозитории есть две ветки, одна под 3.2 версию, вторая под 3.3. И версия под 3.3 более доработанная, автор не повторял свои изменения на 3.2 версии. Я решил сначала перенять опыт, и перенести наработки с 3.3 ветки.
Пришлось поправить init.php модуля. Было:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// include kohana-doctrine config $doctrine_config = Kohana::config('doctrine'); // include Doctrine ClassLoader.php include $doctrine_config['doctrine_path'] . 'Doctrine/Common/ClassLoader.php'; // defines Doctrine namespace $classLoader = new \Doctrine\Common\ClassLoader( 'Doctrine', $doctrine_config['doctrine_path']); $classLoader->register(); // defines Symfony namespace $classLoader = new \Doctrine\Common\ClassLoader( 'Symfony', $doctrine_config['doctrine_path'] . '/Doctrine'); $classLoader->register(); // defines your "entitites" namespace $classLoader = new \Doctrine\Common\ClassLoader( $doctrine_config['entities_namespace'], $doctrine_config['entities_path']); $classLoader->register(); // defines your "proxies" namespace $classLoader = new \Doctrine\Common\ClassLoader( $doctrine_config['proxies_namespace'], $doctrine_config['proxies_path']); $classLoader->register(); // re-use already loaded Doctrine config Doctrine_ORM::set_config($doctrine_config); |
Стало
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// include kohana-doctrine config $doctrine_config = Kohana::$config->load('doctrine'); // Autoload through composer require_once $doctrine_config['vendor_path'] . '/autoload.php'; // defines your "extensions" namespace $classLoader = new \Doctrine\Common\ClassLoader( 'DoctrineExtensions', $doctrine_config['extensions_path']); $classLoader->register(); // Make proxies autoloader work so they work when seralizing objects. // Proxies are not PSR-0 compliant. Doctrine\ORM\Proxy\Autoloader::register($doctrine_config['proxy_dir'], $doctrine_config['proxy_namespace']); // Re-use already loaded Doctrine config Doctrine_ORM::set_config($doctrine_config); |
Из нового тут это extensions_path и vendor_path в конфиге. Я указываю путь до vendor всего проекта, где уже установлена доктрина. Все же, учитывая PEAR стиль названия классов, я не стал делать этот модуль до конца package, и не заливал его в packagist. Подключаю я его прямо из git
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 |
"require": { "php": ">=5.4", "kohana/modules/doctrine": "3.2", ... }, "repositories": [ .... { "type": "package", "package": { "name": "kohana/modules/doctrine", "version": "3.2", "source": { "type": "git", "url": "https://github.com/seyfer/kohana-doctrine.git", "reference": "3.2/master" } } }, .... ], |
В таком режиме подключения composer не читает composer.json пакета, который в данном случае такой.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "name": "kohana/doctrine", "version": "3.2", "description": "Kohana module for 3.2 version to integrate with Doctrine ORM", "author": "Oleg Abrazhaev <seyferseed@mail.ru>", "require": { "php": ">=5.4", "doctrine/orm": "2.4.*", "gedmo/doctrine-extensions": "v2.3.*" } } |
С помощью параметра extensions_path я указываю путь расширений доктрины.
Длаее я перенес наработки с 3.3 для класса Doctrine_ORM. Это основной класс, который используется в клиентском коде. Главное, что я перенес, это метод instance(), делающий singleton реализацию. Без этого была высокая нагрузка на базу даннных, инициализация проходила при каждом вызове Doctrine_ORM.
Так же я внес свои правки. Добавил параметр debug в конфиг. Если ориентировать его на окружение, то в production возникает проблема с генерацией proxy доктрины.
1 2 3 4 |
$isDevMode = self::$doctrineConfig['debug']; $config = Setup::createConfiguration($isDevMode); |
Так же для метода $config->newDefaultAnnotationDriver() я установил использование SimpleAnnotationDriver в false. Иначе не срабатывает использование маппинга as ORM и не парсятся Entity. Так же в качестве проверки существаования классая установил class_exists(), т.к. у классов утсаревшее наименование.
1 2 3 4 5 6 7 |
case 'annotation': $useSimpleAnnotationReader = FALSE; $driver_implementation = $config->newDefaultAnnotationDriver(array(self::$doctrineConfig['mappings_path']), $useSimpleAnnotationReader); AnnotationRegistry::registerLoader('class_exists'); break; |
Настройки подключения к базе данных берутся из стандартного database.php и так же они учитывают группу настроек, по умолчанию default.
Так же я добавил не большой фикс для типа sql – enum.
1 2 3 |
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); |
Еще я добавил методы, которые реализуют проверку подключения текущего EntityManager и его переподключение. Вслучае если при выполнении запроса к БД Doctrine выбрасывает Exception – подключение теряется. Тогда я пользуюсь этими методами.
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 |
/** * check current em and if no connection * recreate * @param type $em * @return type */ protected function checkEMConnection($em) { if (!$em->isOpen()) { $connection = $em->getConnection(); $config = $em->getConfiguration(); return $em->create( $connection, $config ); } } /** * reconnect if needed */ public function reconnectEm() { $newEm = $this->checkEMConnection($this->em); if ($newEm) { $this->em = $newEm; } } |
Следующий файл, который я изменил, это doctrine/bin/doctrine.php
При вызове из консоли встает проблема определения путей к папкам system, application, modules. Они у меня не стандартные. Поэтому я просто добавил конфиг файл path.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php /** * system and app path for cli * @author Oleg Abrazhaev <seyferseed@mail.ru> */ return array( // path 'application' => '/../../../../../application', 'modules' => '/../../../../../vendor/kohana/modules', 'system' => '/../../../../../vendor/kohana/system', ); |
И используею его так.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$path_config = include __DIR__ . '/../config/path.php'; $system = realpath(__DIR__ . $path_config['system']); $application = realpath(__DIR__ . $path_config['application']); $modules = realpath(__DIR__ . $path_config['modules']); if (file_exists($application . "/../index.php")) { $index = file_get_contents($application . "/../index.php"); //replace echo request result $indexFiltered1 = preg_replace("/echo .*/smi", "", $index); //replace php tag for eval $indexFiltered2 = preg_replace("/<\?.*?(\?>|$)/smi", "", $indexFiltered1); eval($indexFiltered2); } else { //if index.php not found initLikeIndex($system, $application, $modules); } |
Старый код я вынес в метод initLikeIndex(), он будет вызываться, если не сработает мой новый метод или нет конфига.
тут стоить пояснить строку с eval. Дело в том, что в конце файла index.php Kohana содержит по умолчанию строки
1 2 3 4 5 6 |
echo Request::factory() ->execute() ->send_headers(TRUE) ->body(); |
И при работе с миграциями (о них дальше) в консоль попадает html вывод. Я не нашел другого способа, кроме как считать этот файл и удалить эти строки, а потом сделать eval() вместо include index.php.
Как использовать модуль?
Подключить стандартным способом. Скопировать doctrine.php в свои конфиги, поменять если надо. Далее создаем папку по пути указанному в конфиге, у меня это APPPATH . ‘classes/doctrine/entity’. Даем права на запись на папку с proxy. Далее можно создавать entity, например
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php use Doctrine\ORM\Mapping as ORM; /** * Users * * @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="uniq_username", columns={"username"}), @ORM\UniqueConstraint(name="uniq_email", columns={"email"})}) * @ORM\Entity */ class Doctrine_Entity_Users extends \Doctrine_Entity_Base { ... } |
Так же создаем папку doctrine/repository и там создаем репозитории, если надо. У меня есть базовый класс репозитория, остальные наследуют от него.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as PaginatorAdapter; use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator; use Zend\Paginator\Paginator as ZendPaginator; use Doctrine\ORM\Query; /** * Description of Doctrine_Repository_Base * * @author seyfer */ class Doctrine_Repository_Base extends EntityRepository { ... } |
В клиентском коде просто вызываем и пользуемся.
1 2 3 4 5 |
$em = Doctrine_ORM::instance()->getEntityManager(); /* @var $repo Doctrine_Repository_Users */ $repo = $em->getRepository('Doctrine_Entity_Users'); |
К сожалению мои правки последний автор не принимает и на связь не выходит. Значит у меня самая последняя версия.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
seyfer commented on May 29 No need to host vendor directory in git repo, because packages installs via composer in global app vendor. So, only need configure path in config. Also use another way to init Doctrine for load entities like Entitiy_Site in ko3.2 seyfer and others added some commits on May 29 Oleg Abrazhaev Rm vendor from module and update init.php and config, to allow load D… … 156802a Oleg Abrazhaev Add simple doctrine class loading and valid annotation driver. ed1fe97 Oleg Abrazhaev Update Readme 4d18192 Oleg Abrazhaev Fix doctrine cli tool df6157e Oleg Abrazhaev Fix error with enums type when generate from db 2e7b6a4 nikulinsasa add instance ccb58a9 Oleg Abrazhaev seyfer commented on Sep 2 Also add Doctrine::instance() method to 3.2 version. seyfer added some commits on Sep 11 Oleg Abrazhaev Add doctrine reconnect method 26bb1eb Oleg Abrazhaev Fix init to more smart 3564d64 Oleg Abrazhaev Fix doctrine instance and config 4990d9f |
Модуль Doctrine Migrations
Адрес seyfer/kohana-doctrinemigrations
С этим модулем несколько проще. Я форкнул его от synchrone/kohana-doctrinemigrations, который в свою очередь форкнул от synapsestudios/kohana-doctrinemigrations. Модуль так же создан 4 года назад.
С последним автором такая же беда. Не выходит на связь. Значит последние правки у меня.
1 2 3 4 5 6 7 8 9 |
seyfer commented on May 30 ...s. Update readme. Add Usage example. seyfer added some commits on May 30 Oleg Abrazhaev Update init to load doctrine from global vendor dir. Add configs exam… … 2db4501 Oleg Abrazhaev Fix config and readme 336e1c7 |
Здесь я так же избавился от vendor в кеше и перешел на vendor проекта. Так же я добавил папку config, которая вообще отсутствовала. Идея этого модуля очень проста. Он зависит от kohana/doctrine описанного выше и просто регистрирует doctrine migration при инициализации. Так же регистрирует console_commands.
Файл init.php
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 |
<?php defined('SYSPATH') or die('No direct script access.'); $modules = Kohana::modules(); $doctrine_config = Kohana::$config->load('doctrine'); require_once $doctrine_config['vendor_path'] . '/autoload.php'; use Doctrine\Common\ClassLoader; $classLoader = new ClassLoader('Doctrine\DBAL\Migrations', $doctrine_config['migrations_path'] . 'lib'); $classLoader->register(); $doctrine_cfg = Kohana::$config->load('doctrine'); $doctrine_cfg->set('console_commands', array_merge($doctrine_cfg->get('console_commands', array()), array( // Migrations Commands new \Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand() ) ) ); $doctrine_cfg->set('console_helpers', array_merge($doctrine_cfg->get('console_helpers', array()), array( 'dialog' => new \Symfony\Component\Console\Helper\DialogHelper(), ) ) ); |
Так же тут я дописал composer.json файл, просто чтобы описать пакет. Так же я добавил конфиг миграций config/migrations.xml, он указывает путь к папке, где они будут создаваться. Путь на чтение указан в конфиге.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="UTF-8"?> <doctrine-migrations xmlns="http://doctrine-project.org/schemas/migrations/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/migrations/configuration http://doctrine-project.org/schemas/migrations/configuration.xsd"> <name>Doctrine Sandbox Migrations</name> <migrations-namespace>Application</migrations-namespace> <table name="doctrine_migration_versions" /> <migrations-directory>application/classes/doctrine/migrations/</migrations-directory> </doctrine-migrations> |
Использование модуля описано в ридми, и оно такое.
1 2 3 4 5 |
php vendor/kohana/modules/doctrine/bin/doctrine migrations:diff --configuration=application/config/migrations.xml php vendor/kohana/modules/doctrine/bin/doctrine migrations:migrate --configuration=application/config/migrations.xml |
Так же я пользуюсь им с параметром --filter-expression="*statistic*", это фильтрует к каким таблицам применить миграцию. Я делаю так, т.к. в моем старом проекте все сделано через Kohana ORM и я перехожу постепенно на Doctrine.
Испольуя модуль Doctrine я так же смог без труда сгенерировать все Entity на существующую базу.