Your SlideShare is downloading. ×

Doctrine 2

5,197

Published on

Валерий Рабиевский …

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

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
5,197
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
38
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Doctrine 2
  • 2. Who am I? Валерий Рабиевский Team lead веб-студии stfalcon.com Активный разработчик Open Source движка ZFEngine (ZF + Doctrine) Более 4 лет опыта работы с PHP
  • 3. Почему? Doctrine 2
  • 4. Библиотеки — Common — DBAL (включает Common) — ORM (включает DBAL+Common) — Migrations (часть DBAL) — Object Document Mapper: MongoDB CouchDB github.com/doctrine
  • 5. Entities — Легкая модель (самый простой PHP класс) — Не нужно наследование от базового класса — Есть возможность наследовать модель от своих базовых классов — Конструктор модели можно использовать для своих нужд — Данные хранятся непосредственно в свойствах объекта
  • 6. Пример модели 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; } }
  • 7. EntityManager EntityManager является центральной точкой доступа к функциям ORM — Управление обновлением сущностей — Доступ к репозиториям сущностей — Используется паттерн UnitOfWork
  • 8. 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
  • 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. Mapping
  • 12. Basic Mapping — Docblock Annotations — 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 — Mapped Superclasses — 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. Console
  • 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 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
  • 19. 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
  • 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. Migrations
  • 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 Накатывание миграции $ ./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
  • 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 Doctrine 1 — Не было реального парсера DQL Doctrine 2 — Abstract Syntax Tree
  • 30. Behaviors
  • 31. Behaviors Нет и не будет расширений «из коробки» Events & Subscribers+ −
  • 32. 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'); } }
  • 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

×