doctrine 2

40
Doctrine 2

Upload: zfconfua

Post on 15-Jan-2015

5.118 views

Category:

Documents


4 download

DESCRIPTION

Валерий Рабиевский Team Leader, stfalcon.com

TRANSCRIPT

Page 1: Doctrine 2

Doctrine 2

Page 2: Doctrine 2

Who am I?

Валерий РабиевскийTeam lead веб-студии stfalcon.com

Активный разработчик Open Source движка ZFEngine (ZF + Doctrine)

Более 4 лет опыта работы с PHP

Page 3: Doctrine 2

Почему?

Doctrine 2

Page 4: Doctrine 2

Библиотеки

— Common— DBAL (включает Common)— ORM (включает DBAL+Common)

— Migrations (часть DBAL)

— Object Document Mapper: MongoDB CouchDB

github.com/doctrine

Page 5: Doctrine 2

Entities

— Легкая модель (самый простой PHP класс)

— Не нужно наследование от базового класса

— Есть возможность наследовать модель от своих базовых классов

— Конструктор модели можно использовать для своих нужд

— Данные хранятся непосредственно в свойствах объекта

Page 6: Doctrine 2

Пример модели

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; } }

Page 7: Doctrine 2

EntityManager

EntityManager является центральной точкой доступа к функциям ORM

— Управление обновлением сущностей

— Доступ к репозиториям сущностей

— Используется паттерн UnitOfWork

Page 8: Doctrine 2

ZF2 + D2

protected function _initAutoload(){ $loader = new \Zend\Loader\StandardAutoloader(); $loader->registerNamespace('Doctrine', '/path/to/Doctrine'); $loader->registerNamespace('Symfony', '/path/to/Symfony'); $loader->register();}

protected function _initAutoload(){ $loader = new \Zend\Loader\StandardAutoloader(); $loader->registerNamespace('Doctrine', '/path/to/Doctrine'); $loader->registerNamespace('Symfony', '/path/to/Symfony'); $loader->register();}

Добавляем автозагрузку в Bootstrap

Page 9: Doctrine 2

Настройка 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="Application\Model\Proxies" /> <autogenerateProxyClasses value="0" /> <cacheAdapter value="Doctrine\Common\Cache\ApcCache" /></doctrine>

<!-- development -->... <autogenerateProxyClasses value="1" /> <cacheAdapter value="Doctrine\Common\Cache\ArrayCache" />…

<!-- production --><doctrine> <connection><!-- user, password, database, etc --></connection> <paths>

<entities>path/to/entities</entities><proxies>path/to/proxies</proxies>

</paths> <proxiesNamespace value="Application\Model\Proxies" /> <autogenerateProxyClasses value="0" /> <cacheAdapter value="Doctrine\Common\Cache\ApcCache" /></doctrine>

<!-- development -->... <autogenerateProxyClasses value="1" /> <cacheAdapter value="Doctrine\Common\Cache\ArrayCache" />…

Page 10: Doctrine 2

Подключение 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; }

Page 11: Doctrine 2

Mapping

Page 12: Doctrine 2

Basic Mapping

— Docblock Annotations

— XML

— YAML

— PHP

Page 13: Doctrine 2

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

Page 14: Doctrine 2

Inheritance Mapping

— Mapped Superclasses

— Single Table Inheritance

— Class Table Inheritance

Page 15: Doctrine 2

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

Page 16: Doctrine 2

Console

Page 17: Doctrine 2

Console

... $em = $application->getBootstrap()->getResource('EntityManager'); ... $helpers = array( 'db' => new DBAL\Helper\ConnectionHelper($em->getConnection()), 'em' => new ORM\Helper\EntityManagerHelper($em), 'dialog' => new \Symfony\Component\Console\Helper\DialogHelper(), ); ... $cli = new \Symfony\Component\Console\Application( 'Doctrine Command Line Interface', Doctrine\Common\Version::VERSION); $cli->setCatchExceptions(true); ... $cli->addCommands(array( new DBAL\Command\RunSqlCommand(), new ORM\Command\ValidateSchemaCommand(), new Migrations\Command\VersionCommand() )); $cli->run();

... $em = $application->getBootstrap()->getResource('EntityManager'); ... $helpers = array( 'db' => new DBAL\Helper\ConnectionHelper($em->getConnection()), 'em' => new ORM\Helper\EntityManagerHelper($em), 'dialog' => new \Symfony\Component\Console\Helper\DialogHelper(), ); ... $cli = new \Symfony\Component\Console\Application( 'Doctrine Command Line Interface', Doctrine\Common\Version::VERSION); $cli->setCatchExceptions(true); ... $cli->addCommands(array( new DBAL\Command\RunSqlCommand(), new ORM\Command\ValidateSchemaCommand(), new Migrations\Command\VersionCommand() )); $cli->run();

Page 18: Doctrine 2

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

Page 19: Doctrine 2

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

Page 20: Doctrine 2

Console: ORM

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

$ ./doctrine orm:validate-schema

[Mapping] FAIL - The entity-class 'Entities\Address' mapping is invalid: * The field Entities\Address#user is on the inverse side of a bi-directional Relationship, but the specified mappedBy association on the target-entity Entities\User#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 'Entities\Address' mapping is invalid: * The field Entities\Address#user is on the inverse side of a bi-directional Relationship, but the specified mappedBy association on the target-entity Entities\User#address does not contain the required 'inversedBy' attribute.

[Database] FAIL - The database schema is not in sync with the current mapping file.

Page 21: Doctrine 2

Migrations

Page 22: Doctrine 2

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>

Page 23: Doctrine 2

Migrations

Доступные команды

$ ./doctrine ... migrations :diff :generate :status :execute :migrate :version ...

$ ./doctrine ... migrations :diff :generate :status :execute :migrate :version ...

Page 24: Doctrine 2

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'); } }

Page 25: Doctrine 2

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

Page 26: Doctrine 2

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'); } }

Page 27: Doctrine 2

Использование

Пример работы с моделями

$em = $this->getInvokeArg('bootstrap') ->getResource('EntityManager');

$address = new Entities\Address(); $address->setStreet('Киевская, 1');

$user = new Entities\User(); $user->setName('Ваня'); $user->setAddress($address);

$em->persist($address); $em->persist($user); $em->flush();

$em = $this->getInvokeArg('bootstrap') ->getResource('EntityManager');

$address = new Entities\Address(); $address->setStreet('Киевская, 1');

$user = new Entities\User(); $user->setName('Ваня'); $user->setAddress($address);

$em->persist($address); $em->persist($user); $em->flush();

Page 28: Doctrine 2

Использование

Пример работы с моделями

$user = $em->find('Entities\User', 1); $user->getAddress(); // → object Proxies\EntitiesAddressProxy

$user->getName(); // Ваня $user->setName('Петя');

$em->flush();

...

$user = $em->find('Entities\User', 1); $user->getName(); // Петя

$user = $em->find('Entities\User', 1); $user->getAddress(); // → object Proxies\EntitiesAddressProxy

$user->getName(); // Ваня $user->setName('Петя');

$em->flush();

...

$user = $em->find('Entities\User', 1); $user->getName(); // Петя

Page 29: Doctrine 2

Doctrine Query Language

Doctrine 1— Не было реального парсера DQL

Doctrine 2— Abstract Syntax Tree

Page 30: Doctrine 2

Behaviors

Page 31: Doctrine 2

Behaviors

Нет и не будет расширений «из коробки»

Events & Subscribers

+−

Page 32: Doctrine 2

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'); } }

Page 33: Doctrine 2

Lifecycle Events

— pre/postRemove

— pre/postPersist

— pre/postUpdate

— postLoad

— loadClassMetadata

— onFlush

Page 34: Doctrine 2

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());

Page 35: Doctrine 2

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; ...

Page 36: Doctrine 2

Репликация

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);}

Page 37: Doctrine 2

Репликация

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);}

Page 38: Doctrine 2

Репликация

В Doctrine 2 все действия с моделью происходят через EntityManager

Значит можно:— создать несколько EM на каждое

подключение;— расширить стандартный EM поддержкой

репликаций;

Page 39: Doctrine 2

Предпоследняя

Исходный код моих экспериментов с ZF2 и Doctrine 2 скоро появится на GitHub'e:

github.com/ftrrtf

Page 40: Doctrine 2

Спасибо за внимание!

Есть вопросы?

Валерий Рабиевский[email protected]

twitter.com/ftrrtffacebook.com/ftrrtf