Doctrine 2
Who am I?
Валерий Рабиевский
Team lead веб-студии stfalcon.com
Активный разработчик Open Source
движка ZFEngine (ZF + Doctrine)
Более 4 лет опыта работы с PHP
Почему?
Doctrine 2
Библиотеки
— Common
— DBAL (включает Common)
— ORM (включает DBAL+Common)
— Migrations (часть DBAL)
— Object Document Mapper:
MongoDB
CouchDB
github.com/doctrine
Entities
— Легкая модель (самый простой PHP класс)
— Не нужно наследование от базового класса
— Есть возможность наследовать модель от
своих базовых классов
— Конструктор модели можно использовать
для своих нужд
— Данные хранятся непосредственно в
свойствах объекта
Пример модели
namespace Entities;
class User
{
private $id;
private $name;
public function getId() { return $this->id; }
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
namespace Entities;
class User
{
private $id;
private $name;
public function getId() { return $this->id; }
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
EntityManager
EntityManager является центральной
точкой доступа к функциям ORM
— Управление обновлением сущностей
— Доступ к репозиториям сущностей
— Используется паттерн UnitOfWork
ZF2 + D2
protected function _initAutoload()
{
$loader = new ZendLoaderStandardAutoloader();
$loader->registerNamespace('Doctrine', '/path/to/Doctrine');
$loader->registerNamespace('Symfony', '/path/to/Symfony');
$loader->register();
}
protected function _initAutoload()
{
$loader = new ZendLoaderStandardAutoloader();
$loader->registerNamespace('Doctrine', '/path/to/Doctrine');
$loader->registerNamespace('Symfony', '/path/to/Symfony');
$loader->register();
}
Добавляем автозагрузку в Bootstrap
Настройка Doctrine 2
./application/configs/application.xml
<!-- production -->
<doctrine>
<connection><!-- user, password, database, etc --></connection>
<paths>
<entities>path/to/entities</entities>
<proxies>path/to/proxies</proxies>
</paths>
<proxiesNamespace value="ApplicationModelProxies" />
<autogenerateProxyClasses value="0" />
<cacheAdapter value="DoctrineCommonCacheApcCache" />
</doctrine>
<!-- development -->
...
<autogenerateProxyClasses value="1" />
<cacheAdapter value="DoctrineCommonCacheArrayCache" />
…
<!-- production -->
<doctrine>
<connection><!-- user, password, database, etc --></connection>
<paths>
<entities>path/to/entities</entities>
<proxies>path/to/proxies</proxies>
</paths>
<proxiesNamespace value="ApplicationModelProxies" />
<autogenerateProxyClasses value="0" />
<cacheAdapter value="DoctrineCommonCacheApcCache" />
</doctrine>
<!-- development -->
...
<autogenerateProxyClasses value="1" />
<cacheAdapter value="DoctrineCommonCacheArrayCache" />
…
Подключение EntityManager
protected function _initEntityManager() {
if (is_null($this->_em)) {
$options = $this->getOption('doctrine');
$cache = new $options['cacheAdapter'];
$config = new Configuration();
$driverImpl = $config
->newDefaultAnnotationDriver($options['paths']['entities']);
$config->setMetadataCacheImpl($cache);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyNamespace($options['proxiesNamespace']);
$config->setProxyDir($options['paths']['proxies']);
$config->setAutoGenerateProxyClasses(
$options['autogenerateProxyClasses']
);
$this->_em =
EntityManager::create($options['connection'], $config);
}
return $this->_em;
}
protected function _initEntityManager() {
if (is_null($this->_em)) {
$options = $this->getOption('doctrine');
$cache = new $options['cacheAdapter'];
$config = new Configuration();
$driverImpl = $config
->newDefaultAnnotationDriver($options['paths']['entities']);
$config->setMetadataCacheImpl($cache);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyNamespace($options['proxiesNamespace']);
$config->setProxyDir($options['paths']['proxies']);
$config->setAutoGenerateProxyClasses(
$options['autogenerateProxyClasses']
);
$this->_em =
EntityManager::create($options['connection'], $config);
}
return $this->_em;
}
Mapping
Basic Mapping
— Docblock Annotations
— XML
— YAML
— PHP
Association Mapping
— One-To-One & Many-To-Many:
— Unidirectional
— Bidirectional
— Self-referencing
— Many-To-One, Unidirectional
— One-To-Many:
— Unidirectional with Join Table
— Bidirectional
— Self-referencing
Inheritance Mapping
— Mapped Superclasses
— Single Table Inheritance
— Class Table Inheritance
Mapping
...
/**
* @ManyToOne(targetEntity="Address", inversedBy="users")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
...
...
/**
* @ManyToOne(targetEntity="Address", inversedBy="users")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
...
...
/** @OneToMany(targetEntity="User", mappedBy="address") */
private $user;
...
...
/** @OneToMany(targetEntity="User", mappedBy="address") */
private $user;
...
Entities/User
Entitites/Address
Console
Console
...
$em = $application->getBootstrap()->getResource('EntityManager');
...
$helpers = array(
'db' => new DBALHelperConnectionHelper($em->getConnection()),
'em' => new ORMHelperEntityManagerHelper($em),
'dialog' => new SymfonyComponentConsoleHelperDialogHelper(),
);
...
$cli = new SymfonyComponentConsoleApplication(
'Doctrine Command Line Interface', DoctrineCommonVersion::VERSION);
$cli->setCatchExceptions(true);
...
$cli->addCommands(array(
new DBALCommandRunSqlCommand(),
new ORMCommandValidateSchemaCommand(),
new MigrationsCommandVersionCommand()
));
$cli->run();
...
$em = $application->getBootstrap()->getResource('EntityManager');
...
$helpers = array(
'db' => new DBALHelperConnectionHelper($em->getConnection()),
'em' => new ORMHelperEntityManagerHelper($em),
'dialog' => new SymfonyComponentConsoleHelperDialogHelper(),
);
...
$cli = new SymfonyComponentConsoleApplication(
'Doctrine Command Line Interface', DoctrineCommonVersion::VERSION);
$cli->setCatchExceptions(true);
...
$cli->addCommands(array(
new DBALCommandRunSqlCommand(),
new ORMCommandValidateSchemaCommand(),
new MigrationsCommandVersionCommand()
));
$cli->run();
Console
$ ./doctrine
Doctrine Command Line Interface version 2.0.0RC3-DEV
Usage:
[options] command [arguments]
dbal
:import
:run-sql
orm
:convert-d1-schema
:convert-mapping
:generate-proxies
:generate-repositories
:run-dql
:validate-schema
orm:clear-cache
:metadata
:query
:result
$ ./doctrine
Doctrine Command Line Interface version 2.0.0RC3-DEV
Usage:
[options] command [arguments]
dbal
:import
:run-sql
orm
:convert-d1-schema
:convert-mapping
:generate-proxies
:generate-repositories
:run-dql
:validate-schema
orm:clear-cache
:metadata
:query
:result
Console: ORM
$ ./doctrine orm:ensure-production-settings
Proxy Classes are always regenerating.
$ ./doctrine orm:ensure-production-settings
SQLSTATE[28000] [1045] Access denied for user 'root'@'localhost'
$ ./doctrine orm:ensure-production-settings
Environment is correctly configured for production.
$ ./doctrine orm:ensure-production-settings
Proxy Classes are always regenerating.
$ ./doctrine orm:ensure-production-settings
SQLSTATE[28000] [1045] Access denied for user 'root'@'localhost'
$ ./doctrine orm:ensure-production-settings
Environment is correctly configured for production.
Проверка корректности настроек
для production
Console: ORM
Валидация модели
$ ./doctrine orm:validate-schema
[Mapping] FAIL - The entity-class 'EntitiesAddress' mapping is invalid:
* The field EntitiesAddress#user is on the inverse side of a
bi-directional Relationship, but the specified mappedBy association
on the target-entity EntitiesUser#address does not contain the
required 'inversedBy' attribute.
[Database] FAIL - The database schema is not in sync with the
current mapping file.
$ ./doctrine orm:validate-schema
[Mapping] FAIL - The entity-class 'EntitiesAddress' mapping is invalid:
* The field EntitiesAddress#user is on the inverse side of a
bi-directional Relationship, but the specified mappedBy association
on the target-entity EntitiesUser#address does not contain the
required 'inversedBy' attribute.
[Database] FAIL - The database schema is not in sync with the
current mapping file.
Migrations
Migrations
Что нужно:
— стандартный скрипт для подключения консоли
— в папку с скриптом добавить migrations.xml (или yaml)
<doctrine-migrations>
<name>Doctrine Migrations</name>
<migrations-namespace>
DoctrineMigrations
</migrations-namespace>
<table name="migration_versions" />
<migrations-directory>/path/to/migrations/</migrations-directory>
</doctrine-migrations>
<doctrine-migrations>
<name>Doctrine Migrations</name>
<migrations-namespace>
DoctrineMigrations
</migrations-namespace>
<table name="migration_versions" />
<migrations-directory>/path/to/migrations/</migrations-directory>
</doctrine-migrations>
Migrations
Доступные команды
$ ./doctrine
...
migrations
:diff :generate :status
:execute :migrate :version
...
$ ./doctrine
...
migrations
:diff :generate :status
:execute :migrate :version
...
Migrations
Фиксируем изменения в миграции
$ ./doctrine migrations:diff
Generated new migration class
to "path/to/migrations/Version20101124201328.php" from schema differences.
$ ./doctrine migrations:diff
Generated new migration class
to "path/to/migrations/Version20101124201328.php" from schema differences.
namespace DoctrineMigrations;
class Version20101124201328 extends AbstractMigration
{
public function up(Schema $schema) {
$this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB');
}
public function down(Schema $schema) {
$this->_addSql('DROP TABLE users');
}
}
namespace DoctrineMigrations;
class Version20101124201328 extends AbstractMigration
{
public function up(Schema $schema) {
$this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB');
}
public function down(Schema $schema) {
$this->_addSql('DROP TABLE users');
}
}
Migrations
Накатывание миграции
$ ./doctrine migrations:migrate --dry-run
Executing dry run of migration up to 20101124201328 from 0
++ migrating 20101124201328
-> CREATE TABLE users ( ... ) ENGINE = InnoDB
++ migrated (0.01s)
------------------------
++ finished in 0.01
++ 1 migrations executed
++ 1 sql queries
$ ./doctrine migrations:migrate --dry-run
Executing dry run of migration up to 20101124201328 from 0
++ migrating 20101124201328
-> CREATE TABLE users ( ... ) ENGINE = InnoDB
++ migrated (0.01s)
------------------------
++ finished in 0.01
++ 1 migrations executed
++ 1 sql queries
Migrations
Генерируем заготовку миграции
$ ./doctrine migrations:generate --editor-cmd=netbeans
Generated new migration class to "path/to/migrations/Version20101124201328.php"
$ ./doctrine migrations:generate --editor-cmd=netbeans
Generated new migration class to "path/to/migrations/Version20101124201328.php"
namespace DoctrineMigrations;
class Version20101124201328 extends AbstractMigration
{
public function up(Schema $schema) {
// $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB');
$table = $schema->createTable('users');
$table->addColumn('username', 'string');
}
public function down(Schema $schema) {
$schema->dropTable('users');
}
}
namespace DoctrineMigrations;
class Version20101124201328 extends AbstractMigration
{
public function up(Schema $schema) {
// $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB');
$table = $schema->createTable('users');
$table->addColumn('username', 'string');
}
public function down(Schema $schema) {
$schema->dropTable('users');
}
}
Использование
Пример работы с моделями
$em = $this->getInvokeArg('bootstrap')
->getResource('EntityManager');
$address = new EntitiesAddress();
$address->setStreet('Киевская, 1');
$user = new EntitiesUser();
$user->setName('Ваня');
$user->setAddress($address);
$em->persist($address);
$em->persist($user);
$em->flush();
$em = $this->getInvokeArg('bootstrap')
->getResource('EntityManager');
$address = new EntitiesAddress();
$address->setStreet('Киевская, 1');
$user = new EntitiesUser();
$user->setName('Ваня');
$user->setAddress($address);
$em->persist($address);
$em->persist($user);
$em->flush();
Использование
Пример работы с моделями
$user = $em->find('EntitiesUser', 1);
$user->getAddress();
// → object ProxiesEntitiesAddressProxy
$user->getName(); // Ваня
$user->setName('Петя');
$em->flush();
...
$user = $em->find('EntitiesUser', 1);
$user->getName(); // Петя
$user = $em->find('EntitiesUser', 1);
$user->getAddress();
// → object ProxiesEntitiesAddressProxy
$user->getName(); // Ваня
$user->setName('Петя');
$em->flush();
...
$user = $em->find('EntitiesUser', 1);
$user->getName(); // Петя
Doctrine Query Language
Doctrine 1
— Не было реального парсера DQL
Doctrine 2
— Abstract Syntax Tree
Behaviors
Behaviors
Нет и не будет
расширений «из коробки»
Events & Subscribers+
−
Events
namespace Entities;
/**
* @HasLifecycleCallbacks
*/
class User {
…
/** @PrePersist */
public function updateCreatedAt()
{
$this->createdAt = date('Y-m-d H:m:s');
}
}
namespace Entities;
/**
* @HasLifecycleCallbacks
*/
class User {
…
/** @PrePersist */
public function updateCreatedAt()
{
$this->createdAt = date('Y-m-d H:m:s');
}
}
Lifecycle Events
— pre/postRemove
— pre/postPersist
— pre/postUpdate
— postLoad
— loadClassMetadata
— onFlush
Event Listeners
Простейший подписчик на события
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents() {
return array(
Events::preUpdate
);
}
public function preUpdate(PreUpdateEventArgs $eventArgs) {
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name')) { /*наш код*/ }
}
}
}
$entityManager->getEventManager()
->addEventSubscriber(new MyEventSubscriber());
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents() {
return array(
Events::preUpdate
);
}
public function preUpdate(PreUpdateEventArgs $eventArgs) {
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name')) { /*наш код*/ }
}
}
}
$entityManager->getEventManager()
->addEventSubscriber(new MyEventSubscriber());
Behavioral Extensions
goo.gl/Mgnwg
(www.doctrine-project.org/blog/doctrine2-behavioral-extensions)
...
/**
* @gedmo:Timestampable(on="create")
* @Column(type="date")
*/
private $created;
/**
* @gedmo:Timestampable(on="update")
* @Column(type="datetime")
*/
private $updated;
...
...
/**
* @gedmo:Timestampable(on="create")
* @Column(type="date")
*/
private $created;
/**
* @gedmo:Timestampable(on="update")
* @Column(type="datetime")
*/
private $updated;
...
Репликация
Doctrine 1
$connections = array(
'master' => 'mysql://root:@master/dbname',
'slave_1' => 'mysql://root:@slave1/dbname',
'slave_2' => 'mysql://root:@slave2/dbname',
);
foreach ($connections as $name => $dsn) {
Doctrine_Manager::connection($dsn, $name);
}
$connections = array(
'master' => 'mysql://root:@master/dbname',
'slave_1' => 'mysql://root:@slave1/dbname',
'slave_2' => 'mysql://root:@slave2/dbname',
);
foreach ($connections as $name => $dsn) {
Doctrine_Manager::connection($dsn, $name);
}
Репликация
Doctrine 1
Doctrine 2
:(
$connections = array(
'master' => 'mysql://root:@master/dbname',
'slave_1' => 'mysql://root:@slave1/dbname',
'slave_2' => 'mysql://root:@slave2/dbname',
);
foreach ($connections as $name => $dsn) {
Doctrine_Manager::connection($dsn, $name);
}
$connections = array(
'master' => 'mysql://root:@master/dbname',
'slave_1' => 'mysql://root:@slave1/dbname',
'slave_2' => 'mysql://root:@slave2/dbname',
);
foreach ($connections as $name => $dsn) {
Doctrine_Manager::connection($dsn, $name);
}
Репликация
В Doctrine 2 все действия с моделью
происходят через EntityManager
Значит можно:
— создать несколько EM на каждое
подключение;
— расширить стандартный EM поддержкой
репликаций;
Предпоследняя
Исходный код моих экспериментов с ZF2 и
Doctrine 2 скоро появится на GitHub'e:
github.com/ftrrtf
Спасибо за внимание!
Есть вопросы?
Валерий Рабиевский
mail@ftrrtf.com
twitter.com/ftrrtf
facebook.com/ftrrtf

Doctrine 2

  • 1.
  • 2.
    Who am I? ВалерийРабиевский Team lead веб-студии stfalcon.com Активный разработчик Open Source движка ZFEngine (ZF + Doctrine) Более 4 лет опыта работы с PHP
  • 3.
  • 4.
    Библиотеки — Common — DBAL(включает Common) — ORM (включает DBAL+Common) — Migrations (часть DBAL) — Object Document Mapper: MongoDB CouchDB github.com/doctrine
  • 5.
    Entities — Легкая модель(самый простой PHP класс) — Не нужно наследование от базового класса — Есть возможность наследовать модель от своих базовых классов — Конструктор модели можно использовать для своих нужд — Данные хранятся непосредственно в свойствах объекта
  • 6.
    Пример модели namespace Entities; classUser { private $id; private $name; public function getId() { return $this->id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } } namespace Entities; class User { private $id; private $name; public function getId() { return $this->id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } }
  • 7.
    EntityManager EntityManager является центральной точкойдоступа к функциям ORM — Управление обновлением сущностей — Доступ к репозиториям сущностей — Используется паттерн UnitOfWork
  • 8.
    ZF2 + D2 protectedfunction _initAutoload() { $loader = new ZendLoaderStandardAutoloader(); $loader->registerNamespace('Doctrine', '/path/to/Doctrine'); $loader->registerNamespace('Symfony', '/path/to/Symfony'); $loader->register(); } protected function _initAutoload() { $loader = new ZendLoaderStandardAutoloader(); $loader->registerNamespace('Doctrine', '/path/to/Doctrine'); $loader->registerNamespace('Symfony', '/path/to/Symfony'); $loader->register(); } Добавляем автозагрузку в Bootstrap
  • 9.
    Настройка Doctrine 2 ./application/configs/application.xml <!--production --> <doctrine> <connection><!-- user, password, database, etc --></connection> <paths> <entities>path/to/entities</entities> <proxies>path/to/proxies</proxies> </paths> <proxiesNamespace value="ApplicationModelProxies" /> <autogenerateProxyClasses value="0" /> <cacheAdapter value="DoctrineCommonCacheApcCache" /> </doctrine> <!-- development --> ... <autogenerateProxyClasses value="1" /> <cacheAdapter value="DoctrineCommonCacheArrayCache" /> … <!-- production --> <doctrine> <connection><!-- user, password, database, etc --></connection> <paths> <entities>path/to/entities</entities> <proxies>path/to/proxies</proxies> </paths> <proxiesNamespace value="ApplicationModelProxies" /> <autogenerateProxyClasses value="0" /> <cacheAdapter value="DoctrineCommonCacheApcCache" /> </doctrine> <!-- development --> ... <autogenerateProxyClasses value="1" /> <cacheAdapter value="DoctrineCommonCacheArrayCache" /> …
  • 10.
    Подключение EntityManager protected function_initEntityManager() { if (is_null($this->_em)) { $options = $this->getOption('doctrine'); $cache = new $options['cacheAdapter']; $config = new Configuration(); $driverImpl = $config ->newDefaultAnnotationDriver($options['paths']['entities']); $config->setMetadataCacheImpl($cache); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyNamespace($options['proxiesNamespace']); $config->setProxyDir($options['paths']['proxies']); $config->setAutoGenerateProxyClasses( $options['autogenerateProxyClasses'] ); $this->_em = EntityManager::create($options['connection'], $config); } return $this->_em; } protected function _initEntityManager() { if (is_null($this->_em)) { $options = $this->getOption('doctrine'); $cache = new $options['cacheAdapter']; $config = new Configuration(); $driverImpl = $config ->newDefaultAnnotationDriver($options['paths']['entities']); $config->setMetadataCacheImpl($cache); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyNamespace($options['proxiesNamespace']); $config->setProxyDir($options['paths']['proxies']); $config->setAutoGenerateProxyClasses( $options['autogenerateProxyClasses'] ); $this->_em = EntityManager::create($options['connection'], $config); } return $this->_em; }
  • 11.
  • 12.
    Basic Mapping — DocblockAnnotations — XML — YAML — PHP
  • 13.
    Association Mapping — One-To-One& Many-To-Many: — Unidirectional — Bidirectional — Self-referencing — Many-To-One, Unidirectional — One-To-Many: — Unidirectional with Join Table — Bidirectional — Self-referencing
  • 14.
    Inheritance Mapping — MappedSuperclasses — Single Table Inheritance — Class Table Inheritance
  • 15.
    Mapping ... /** * @ManyToOne(targetEntity="Address", inversedBy="users") *@JoinColumn(name="address_id", referencedColumnName="id") */ private $address; ... ... /** * @ManyToOne(targetEntity="Address", inversedBy="users") * @JoinColumn(name="address_id", referencedColumnName="id") */ private $address; ... ... /** @OneToMany(targetEntity="User", mappedBy="address") */ private $user; ... ... /** @OneToMany(targetEntity="User", mappedBy="address") */ private $user; ... Entities/User Entitites/Address
  • 16.
  • 17.
    Console ... $em = $application->getBootstrap()->getResource('EntityManager'); ... $helpers= array( 'db' => new DBALHelperConnectionHelper($em->getConnection()), 'em' => new ORMHelperEntityManagerHelper($em), 'dialog' => new SymfonyComponentConsoleHelperDialogHelper(), ); ... $cli = new SymfonyComponentConsoleApplication( 'Doctrine Command Line Interface', DoctrineCommonVersion::VERSION); $cli->setCatchExceptions(true); ... $cli->addCommands(array( new DBALCommandRunSqlCommand(), new ORMCommandValidateSchemaCommand(), new MigrationsCommandVersionCommand() )); $cli->run(); ... $em = $application->getBootstrap()->getResource('EntityManager'); ... $helpers = array( 'db' => new DBALHelperConnectionHelper($em->getConnection()), 'em' => new ORMHelperEntityManagerHelper($em), 'dialog' => new SymfonyComponentConsoleHelperDialogHelper(), ); ... $cli = new SymfonyComponentConsoleApplication( 'Doctrine Command Line Interface', DoctrineCommonVersion::VERSION); $cli->setCatchExceptions(true); ... $cli->addCommands(array( new DBALCommandRunSqlCommand(), new ORMCommandValidateSchemaCommand(), new MigrationsCommandVersionCommand() )); $cli->run();
  • 18.
    Console $ ./doctrine Doctrine CommandLine Interface version 2.0.0RC3-DEV Usage: [options] command [arguments] dbal :import :run-sql orm :convert-d1-schema :convert-mapping :generate-proxies :generate-repositories :run-dql :validate-schema orm:clear-cache :metadata :query :result $ ./doctrine Doctrine Command Line Interface version 2.0.0RC3-DEV Usage: [options] command [arguments] dbal :import :run-sql orm :convert-d1-schema :convert-mapping :generate-proxies :generate-repositories :run-dql :validate-schema orm:clear-cache :metadata :query :result
  • 19.
    Console: ORM $ ./doctrineorm:ensure-production-settings Proxy Classes are always regenerating. $ ./doctrine orm:ensure-production-settings SQLSTATE[28000] [1045] Access denied for user 'root'@'localhost' $ ./doctrine orm:ensure-production-settings Environment is correctly configured for production. $ ./doctrine orm:ensure-production-settings Proxy Classes are always regenerating. $ ./doctrine orm:ensure-production-settings SQLSTATE[28000] [1045] Access denied for user 'root'@'localhost' $ ./doctrine orm:ensure-production-settings Environment is correctly configured for production. Проверка корректности настроек для production
  • 20.
    Console: ORM Валидация модели $./doctrine orm:validate-schema [Mapping] FAIL - The entity-class 'EntitiesAddress' mapping is invalid: * The field EntitiesAddress#user is on the inverse side of a bi-directional Relationship, but the specified mappedBy association on the target-entity EntitiesUser#address does not contain the required 'inversedBy' attribute. [Database] FAIL - The database schema is not in sync with the current mapping file. $ ./doctrine orm:validate-schema [Mapping] FAIL - The entity-class 'EntitiesAddress' mapping is invalid: * The field EntitiesAddress#user is on the inverse side of a bi-directional Relationship, but the specified mappedBy association on the target-entity EntitiesUser#address does not contain the required 'inversedBy' attribute. [Database] FAIL - The database schema is not in sync with the current mapping file.
  • 21.
  • 22.
    Migrations Что нужно: — стандартныйскрипт для подключения консоли — в папку с скриптом добавить migrations.xml (или yaml) <doctrine-migrations> <name>Doctrine Migrations</name> <migrations-namespace> DoctrineMigrations </migrations-namespace> <table name="migration_versions" /> <migrations-directory>/path/to/migrations/</migrations-directory> </doctrine-migrations> <doctrine-migrations> <name>Doctrine Migrations</name> <migrations-namespace> DoctrineMigrations </migrations-namespace> <table name="migration_versions" /> <migrations-directory>/path/to/migrations/</migrations-directory> </doctrine-migrations>
  • 23.
    Migrations Доступные команды $ ./doctrine ... migrations :diff:generate :status :execute :migrate :version ... $ ./doctrine ... migrations :diff :generate :status :execute :migrate :version ...
  • 24.
    Migrations Фиксируем изменения вмиграции $ ./doctrine migrations:diff Generated new migration class to "path/to/migrations/Version20101124201328.php" from schema differences. $ ./doctrine migrations:diff Generated new migration class to "path/to/migrations/Version20101124201328.php" from schema differences. namespace DoctrineMigrations; class Version20101124201328 extends AbstractMigration { public function up(Schema $schema) { $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB'); } public function down(Schema $schema) { $this->_addSql('DROP TABLE users'); } } namespace DoctrineMigrations; class Version20101124201328 extends AbstractMigration { public function up(Schema $schema) { $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB'); } public function down(Schema $schema) { $this->_addSql('DROP TABLE users'); } }
  • 25.
    Migrations Накатывание миграции $ ./doctrinemigrations:migrate --dry-run Executing dry run of migration up to 20101124201328 from 0 ++ migrating 20101124201328 -> CREATE TABLE users ( ... ) ENGINE = InnoDB ++ migrated (0.01s) ------------------------ ++ finished in 0.01 ++ 1 migrations executed ++ 1 sql queries $ ./doctrine migrations:migrate --dry-run Executing dry run of migration up to 20101124201328 from 0 ++ migrating 20101124201328 -> CREATE TABLE users ( ... ) ENGINE = InnoDB ++ migrated (0.01s) ------------------------ ++ finished in 0.01 ++ 1 migrations executed ++ 1 sql queries
  • 26.
    Migrations Генерируем заготовку миграции $./doctrine migrations:generate --editor-cmd=netbeans Generated new migration class to "path/to/migrations/Version20101124201328.php" $ ./doctrine migrations:generate --editor-cmd=netbeans Generated new migration class to "path/to/migrations/Version20101124201328.php" namespace DoctrineMigrations; class Version20101124201328 extends AbstractMigration { public function up(Schema $schema) { // $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB'); $table = $schema->createTable('users'); $table->addColumn('username', 'string'); } public function down(Schema $schema) { $schema->dropTable('users'); } } namespace DoctrineMigrations; class Version20101124201328 extends AbstractMigration { public function up(Schema $schema) { // $this->_addSql('CREATE TABLE users (...) ENGINE = InnoDB'); $table = $schema->createTable('users'); $table->addColumn('username', 'string'); } public function down(Schema $schema) { $schema->dropTable('users'); } }
  • 27.
    Использование Пример работы смоделями $em = $this->getInvokeArg('bootstrap') ->getResource('EntityManager'); $address = new EntitiesAddress(); $address->setStreet('Киевская, 1'); $user = new EntitiesUser(); $user->setName('Ваня'); $user->setAddress($address); $em->persist($address); $em->persist($user); $em->flush(); $em = $this->getInvokeArg('bootstrap') ->getResource('EntityManager'); $address = new EntitiesAddress(); $address->setStreet('Киевская, 1'); $user = new EntitiesUser(); $user->setName('Ваня'); $user->setAddress($address); $em->persist($address); $em->persist($user); $em->flush();
  • 28.
    Использование Пример работы смоделями $user = $em->find('EntitiesUser', 1); $user->getAddress(); // → object ProxiesEntitiesAddressProxy $user->getName(); // Ваня $user->setName('Петя'); $em->flush(); ... $user = $em->find('EntitiesUser', 1); $user->getName(); // Петя $user = $em->find('EntitiesUser', 1); $user->getAddress(); // → object ProxiesEntitiesAddressProxy $user->getName(); // Ваня $user->setName('Петя'); $em->flush(); ... $user = $em->find('EntitiesUser', 1); $user->getName(); // Петя
  • 29.
    Doctrine Query Language Doctrine1 — Не было реального парсера DQL Doctrine 2 — Abstract Syntax Tree
  • 30.
  • 31.
    Behaviors Нет и небудет расширений «из коробки» Events & Subscribers+ −
  • 32.
    Events namespace Entities; /** * @HasLifecycleCallbacks */ classUser { … /** @PrePersist */ public function updateCreatedAt() { $this->createdAt = date('Y-m-d H:m:s'); } } namespace Entities; /** * @HasLifecycleCallbacks */ class User { … /** @PrePersist */ public function updateCreatedAt() { $this->createdAt = date('Y-m-d H:m:s'); } }
  • 33.
    Lifecycle Events — pre/postRemove —pre/postPersist — pre/postUpdate — postLoad — loadClassMetadata — onFlush
  • 34.
    Event Listeners Простейший подписчикна события class MyEventSubscriber implements EventSubscriber { public function getSubscribedEvents() { return array( Events::preUpdate ); } public function preUpdate(PreUpdateEventArgs $eventArgs) { if ($eventArgs->getEntity() instanceof User) { if ($eventArgs->hasChangedField('name')) { /*наш код*/ } } } } $entityManager->getEventManager() ->addEventSubscriber(new MyEventSubscriber()); class MyEventSubscriber implements EventSubscriber { public function getSubscribedEvents() { return array( Events::preUpdate ); } public function preUpdate(PreUpdateEventArgs $eventArgs) { if ($eventArgs->getEntity() instanceof User) { if ($eventArgs->hasChangedField('name')) { /*наш код*/ } } } } $entityManager->getEventManager() ->addEventSubscriber(new MyEventSubscriber());
  • 35.
    Behavioral Extensions goo.gl/Mgnwg (www.doctrine-project.org/blog/doctrine2-behavioral-extensions) ... /** * @gedmo:Timestampable(on="create") *@Column(type="date") */ private $created; /** * @gedmo:Timestampable(on="update") * @Column(type="datetime") */ private $updated; ... ... /** * @gedmo:Timestampable(on="create") * @Column(type="date") */ private $created; /** * @gedmo:Timestampable(on="update") * @Column(type="datetime") */ private $updated; ...
  • 36.
    Репликация Doctrine 1 $connections =array( 'master' => 'mysql://root:@master/dbname', 'slave_1' => 'mysql://root:@slave1/dbname', 'slave_2' => 'mysql://root:@slave2/dbname', ); foreach ($connections as $name => $dsn) { Doctrine_Manager::connection($dsn, $name); } $connections = array( 'master' => 'mysql://root:@master/dbname', 'slave_1' => 'mysql://root:@slave1/dbname', 'slave_2' => 'mysql://root:@slave2/dbname', ); foreach ($connections as $name => $dsn) { Doctrine_Manager::connection($dsn, $name); }
  • 37.
    Репликация Doctrine 1 Doctrine 2 :( $connections= array( 'master' => 'mysql://root:@master/dbname', 'slave_1' => 'mysql://root:@slave1/dbname', 'slave_2' => 'mysql://root:@slave2/dbname', ); foreach ($connections as $name => $dsn) { Doctrine_Manager::connection($dsn, $name); } $connections = array( 'master' => 'mysql://root:@master/dbname', 'slave_1' => 'mysql://root:@slave1/dbname', 'slave_2' => 'mysql://root:@slave2/dbname', ); foreach ($connections as $name => $dsn) { Doctrine_Manager::connection($dsn, $name); }
  • 38.
    Репликация В Doctrine 2все действия с моделью происходят через EntityManager Значит можно: — создать несколько EM на каждое подключение; — расширить стандартный EM поддержкой репликаций;
  • 39.
    Предпоследняя Исходный код моихэкспериментов с ZF2 и Doctrine 2 скоро появится на GitHub'e: github.com/ftrrtf
  • 40.
    Спасибо за внимание! Естьвопросы? Валерий Рабиевский mail@ftrrtf.com twitter.com/ftrrtf facebook.com/ftrrtf