Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Doctrine - more than an ORM (Symfony User Group Osnabrück edition)

Doctrine - more than an ORM (Symfony User Group Osnabrück edition)

7ea0eec719c20e8d7880bfdb35f78b4e?s=128

alcaeus

May 09, 2019
Tweet

Transcript

  1. Doctrine More than just an ORM Andreas Braun @alcaeus

  2. None
  3. Let’s go back in time

  4. None
  5. class Email extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('id',

    'integer'); $this->hasColumn('email', 'string'); } public function setUp() { $this->hasOne('User', array( 'local' => 'user_id', 'foreign' => 'id' )); } }
  6. $email = new Email(); $email->email = 'doctrine@example.com'; $email->user = new

    User(); $email->save();
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. A look below the surface

  17. $ composer require doctrine/orm Using version ^2.6 for doctrine/orm Package

    operations: 14 installs, 0 updates, 0 removals - Installing symfony/polyfill-mbstring (v1.9.0) - Installing symfony/console (v4.1.6) - Installing doctrine/instantiator (1.1.0) - Installing doctrine/event-manager (v1.0.0) - Installing doctrine/cache (v1.8.0) - Installing doctrine/dbal (v2.8.0) - Installing doctrine/lexer (v1.0.1) - Installing doctrine/annotations (v1.6.0) - Installing doctrine/reflection (v1.0.0) - Installing doctrine/collections (v1.5.0) - Installing doctrine/persistence (v1.0.1) - Installing doctrine/inflector (v1.3.0) - Installing doctrine/common (v2.9.0) - Installing doctrine/orm (v2.6.2) Writing lock file Generating autoload files
  18. $ composer require symfony/maker-bundle [...] $ ./bin/console make:entity Email created:

    src/Entity/Email.php created: src/Repository/EmailRepository.php Entity generated! Now let's add some fields! New property name (press <return> to stop adding fields): > email Field type (enter ? to see all types) [string]: > Field length [255]: > Can this field be null in the database (nullable) (yes/no) [no]: >
  19. class Email { private $id; private $email; public function __construct(string

    $email) { $this->email = $email; } public function getEmail(): string { return $this->email; } }
  20. /** * @method Email|null find($id, $lockMode = null, $lockVersion =

    null) * @method Email|null findOneBy(array $criteria, array $orderBy = null) * @method Email[] findAll() * @method Email[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ class EmailRepository extends ServiceEntityRepository { public function __construct(RegistryInterface $registry) { parent::__construct($registry, Email::class); } }
  21. namespace Doctrine\Common\Persistence; /** * Contract for a Doctrine persistence layer

    ObjectManager class to implement. */ interface ObjectManager {} /** * Contract for a Doctrine persistence layer repository class to implement. */ interface ObjectRepository {} /** * Contract for a Doctrine persistence layer ClassMetadata class to implement. */ interface ClassMetadata {} /** * Interface for proxy classes. */ interface Proxy {}
  22. /** * @ORM\Entity(repositoryClass=EmailRepository::class) */ class Email { /** * @ORM\Id()

    * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $email; }
  23. None
  24. /** * @Annotation * @Target("CLASS") */ final class SuperDuperClass {

    /** * @Required */ public $level; }
  25. public function getSuperDuperClassLevel(string $className): ?int { $reader = new AnnotationReader();

    $classAnnotations = $reader->getClassAnnotations(new ReflectionClass($className)); foreach ($classAnnotations as $annotation) { if ($annotation instanceof SuperDuperClass) { return $annotation->level; } } return null; }
  26. use App\Annotation\SuperDuperClass; use Doctrine\ORM\Mapping as ORM; /** * @SuperDuperClass(level=5) *

    @ORM\Entity(repositoryClass=EmailRepository::class) */ class Email
  27. class User { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer")

    */ private $id; /** * @ORM\Column(type="string", length=255) */ private $username; /** * @ORM\OneToMany(targetEntity=Email::class, mappedBy="user", orphanRemoval=true) */ private $emails; }
  28. /** * @return Collection|Email[] */ public function getEmails(): Collection {

    return $this->emails; } public function addEmail(Email $email): void { if (!$this->emails->contains($email)) { $this->emails[] = $email; $email->setUser($this); } }
  29. None
  30. /** * Returns a word in singular form. * *

    @param string $word The word in plural form. * * @return string The word in singular form. */ public function singularize(string $word) : string; /** * Returns a word in plural form. * * @param string $word The word in singular form. * * @return string The word in plural form. */ public function pluralize(string $word) : string;
  31. None
  32. $ ./bin/console make:migration Success! Next: Review the new migration "src/Migrations/Version20180922042910.php"

    Then: Run the migration with php bin/console doctrine:migrations:migrate See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
  33. None
  34. final class Version20180922042910 extends AbstractMigration { public function up(Schema $schema)

    : void { // ... } public function down(Schema $schema) : void { $this->addSql('ALTER TABLE email DROP FOREIGN KEY FK_E7927C74A76ED395'); $this->addSql('DROP TABLE email'); $this->addSql('DROP TABLE user'); } }
  35. $ ./bin/console doctrine:migrations:migrate Application Migrations Migrating up to 20180922042910 from

    0 ++ migrating 20180922042910 -> CREATE TABLE email [...] -> CREATE TABLE user [...] -> ALTER TABLE email ADD CONSTRAINT [...] ++ migrated (0.17s) ------------------------ ++ finished in 0.17s ++ 1 migrations executed ++ 3 sql queries
  36. None
  37. doctrine: dbal: # configure these for your database server driver:

    'pdo_mysql' server_version: '5.7' charset: utf8mb4 default_table_options: charset: utf8mb4 collate: utf8mb4_unicode_ci url: '%env(resolve:DATABASE_URL)%'
  38. public function getListTableForeignKeysSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName());

    return "SELECT alc.constraint_name, alc.DELETE_RULE, cols.column_name \"local_column\", cols.position, ( SELECT r_cols.table_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"references_table\", ( SELECT r_cols.column_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"foreign_column\" FROM user_cons_columns cols JOIN user_constraints alc ON alc.constraint_name = cols.constraint_name AND alc.constraint_type = 'R' AND alc.table_name = " . $table . " ORDER BY cols.constraint_name ASC, cols.position ASC"; }
  39. - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing doctrine/annotations

    (v1.6.0): Loading from cache - Installing doctrine/event-manager (v1.0.0): Loading from cache - Installing doctrine/collections (v1.5.0): Loading from cache - Installing doctrine/cache (v1.8.0): Loading from cache - Installing doctrine/persistence (v1.0.1): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/common (v2.9.0): Loading from cache - Installing doctrine/instantiator (1.1.0): Loading from cache - Installing doctrine/dbal (v2.8.0): Loading from cache - Installing doctrine/migrations (v1.8.1): Loading from cache - Installing doctrine/orm (v2.6.2): Loading from cache
  40. - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing doctrine/annotations

    (v1.6.0): Loading from cache - Installing doctrine/event-manager (v1.0.0): Loading from cache - Installing doctrine/collections (v1.5.0): Loading from cache - Installing doctrine/cache (v1.8.0): Loading from cache - Installing doctrine/persistence (v1.0.1): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/common (v2.9.0): Loading from cache - Installing doctrine/instantiator (1.1.0): Loading from cache - Installing doctrine/dbal (v2.8.0): Loading from cache - Installing doctrine/migrations (v1.8.1): Loading from cache - Installing doctrine/orm (v2.6.2): Loading from cache
  41. None
  42. /** * @Route("/signup", name="signup") */ public function __invoke(EntityManager $em, string

    $username, string $email): Response { $user = new User($username); $user->addEmail(new Email($email)); $em->persist($user); $em->flush(); return $this->json([], 204); }
  43. /** * @ORM\OneToMany(targetEntity="App\Entity\Email", mappedBy="user", orphanRemoval=true) */ private $emails; public function

    __construct() { $this->emails = new ArrayCollection(); } /** * @return Collection|Email[] */ public function getEmails(): Collection { return $this->emails; } public function addEmail(Email $email): self { if (!$this->emails->contains($email)) { $this->emails[] = $email; $email->setUser($this); } return $this; }
  44. None
  45. interface Collection extends Countable, IteratorAggregate, ArrayAccess { public function add($element);

    public function clear(); public function contains($element); public function isEmpty(); public function remove($key); // ... }
  46. public function takeSnapshot() { $this->snapshot = $this->collection->toArray(); $this->isDirty = false;

    } public function getDeleteDiff() { return array_udiff_assoc( $this->snapshot, $this->collection->toArray(), function($a, $b) { return $a === $b ? 0 : 1; } ); } public function getInsertDiff() { return array_udiff_assoc( $this->collection->toArray(), $this->snapshot, function($a, $b) { return $a === $b ? 0 : 1; } ); }
  47. class UserRepository extends ServiceEntityRepository { public function findOneByUsername(string $username): ?User

    { return $this->createQueryBuilder('u') ->andWhere('u.username = :username') ->setParameter('username', $username) ->getQuery() ->getOneOrNullResult() ; } }
  48. class UserRepository extends ServiceEntityRepository { public function findOneByUsername(string $username): ?User

    { return $this->getEntityManager() ->createQuery('SELECT u FROM App\User u WHERE u.username = :username') ->setParameter('username', $username) ->getOneOrNullResult() ; } }
  49. None
  50. protected function getCatchablePatterns() { return [ '[a-z_][a-z0-9_]*\:[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // aliased name

    '[a-z_\\\][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers "'(?:[^']|'')*'", // quoted strings '\?[0-9]*|:[a-z_][a-z0-9_]*' // parameters ]; }
  51. None
  52. doctrine: orm: metadata_cache_driver: type: memcache class: Doctrine\Common\Cache\MemcacheCache host: localhost port:

    11211 instance_class: Memcache query_cache_driver: type: memcache class: Doctrine\Common\Cache\MemcacheCache host: localhost port: 11211 instance_class: Memcache result_cache_driver: type: redis class: Doctrine\Common\Cache\RedisCache host: localhost port: 6379 database: 1 instance_class: Redis
  53. doctrine: orm: metadata_cache_driver: type: service id: doctrine.system_cache_provider query_cache_driver: type: service

    id: doctrine.system_cache_provider result_cache_driver: type: service id: doctrine.result_cache_provider services: doctrine.result_cache_provider: class: Symfony\Component\Cache\DoctrineProvider public: false arguments: - '@doctrine.result_cache_pool' doctrine.system_cache_provider: class: Symfony\Component\Cache\DoctrineProvider public: false arguments: - ‘@doctrine.system_cache_pool' framework: cache: pools: doctrine.result_cache_pool: adapter: cache.app doctrine.system_cache_pool: adapter: cache.system
  54. doctrine: orm: metadata_cache_driver: type: pool pool: doctrine.system_cache_pool query_cache_driver: type: pool

    pool: doctrine.system_cache_provider result_cache_driver: type: pool pool: doctrine.result_cache_pool framework: cache: pools: doctrine.result_cache_pool: adapter: cache.app doctrine.system_cache_pool: adapter: cache.system
  55. None
  56. None
  57. $serializedString = sprintf( '%s:%d:"%s":0:{}', self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className ); return unserialize($serializedString);

  58. use Doctrine\Instantiator\Instantiator; final class Dummy { private $foo; public function

    __construct(string $foo) { $this->foo = $foo; } public function getFoo(): string { return $this->foo; } } (new Instantiator())->instantiate(Dummy::class);
  59. class Dummy#4 (1) { private $foo => NULL } Uncaught

    TypeError: Return value of Dummy::getFoo() must be of the type string, null returned
  60. $ composer require stof/doctrine-extensions-bundle Using version ^1.3 for stof/doctrine-extensions-bundle ./composer.json

    has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Restricting packages listed in "symfony/symfony" to "4.1.*" Prefetching 3 packages - Downloading (100%) Package operations: 3 installs, 0 updates, 0 removals - Installing behat/transliterator (v1.2.0): Loading from cache - Installing gedmo/doctrine-extensions (v2.4.36): Loading from cache - Installing stof/doctrine-extensions-bundle (v1.3.0): Loading from cache
  61. class User { // ... /** * @ORM\Column(type="datetime") * @Gedmo\Timestampable(on="create")

    */ private $createdAt; /** * @ORM\Column(type="datetime") * @Gedmo\Timestampable(on=“update") */ private $updatedAt; }
  62. final class Events { const preRemove = 'preRemove'; const postRemove

    = 'postRemove'; const prePersist = 'prePersist'; const postPersist = 'postPersist'; const preUpdate = 'preUpdate'; const postUpdate = 'postUpdate'; const postLoad = 'postLoad'; const loadClassMetadata = 'loadClassMetadata'; const onClassMetadataNotFound = 'onClassMetadataNotFound'; const preFlush = 'preFlush'; const onFlush = 'onFlush'; const postFlush = 'postFlush'; const onClear = 'onClear'; }
  63. None
  64. - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing doctrine/annotations

    (v1.6.0): Loading from cache - Installing doctrine/event-manager (v1.0.0): Loading from cache - Installing doctrine/collections (v1.5.0): Loading from cache - Installing doctrine/cache (v1.8.0): Loading from cache - Installing doctrine/persistence (v1.0.1): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/common (v2.9.0): Loading from cache - Installing doctrine/instantiator (1.1.0): Loading from cache - Installing doctrine/dbal (v2.8.0): Loading from cache - Installing doctrine/migrations (v1.8.1): Loading from cache - Installing doctrine/orm (v2.6.2): Loading from cache
  65. - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing doctrine/annotations

    (v1.6.0): Loading from cache - Installing doctrine/event-manager (v1.0.0): Loading from cache - Installing doctrine/collections (v1.5.0): Loading from cache - Installing doctrine/cache (v1.8.0): Loading from cache - Installing doctrine/persistence (v1.0.1): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/common (v2.9.0): Loading from cache - Installing doctrine/instantiator (1.1.0): Loading from cache - Installing doctrine/dbal (v2.8.0): Loading from cache - Installing doctrine/migrations (v1.8.1): Loading from cache - Installing doctrine/orm (v2.6.2): Loading from cache
  66. $ composer require doctrine/coding-standard Using version ^6.0 for doctrine/coding-standard ./composer.json

    has been updated Loading composer repositories with package information Updating dependencies (including require-dev)
  67. None
  68. /** * @author alcaeus * @since 1.0 */ final class

    Email { private $email; /** * @param string $email * @return void */ public function setEmail($email) { $this->email = $email; } /** * @return string|null */ public function getEmail() { return $this->email; } }
  69. $ vendor/bin/phpcbf src/Email.php PHPCBF RESULT SUMMARY ---------------------------------------------------------------------- FILE FIXED REMAINING

    ---------------------------------------------------------------------- src/Email.php 7 1 ---------------------------------------------------------------------- A TOTAL OF 7 ERRORS WERE FIXED IN 1 FILE ---------------------------------------------------------------------- Time: 203ms; Memory: 10Mb $ vendor/bin/phpcs src/Email.php FILE: src/Email.php ------------------------------------------------------------------------ FOUND 1 ERROR AFFECTING 1 LINE ------------------------------------------------------------------------ 9 | ERROR | Property \App\Email::$email does not have @var annotation. ------------------------------------------------------------------------ Time: 124ms; Memory: 8Mb
  70. final class Email { private $email; public function setEmail(string $email)

    : void { $this->email = $email; } public function getEmail() : ?string { return $this->email; } }
  71. <ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"> <rule ref="Doctrine"/> <!-- Require no space around

    colon in return types --> <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing"> <properties> <property name="spacesCountBeforeColon" value="0"/> </properties> </rule> <!-- ... --> </ruleset>
  72. None
  73. None
  74. None
  75. None
  76. None
  77. None
  78. None
  79. None
  80. None
  81. None
  82. None
  83. None
  84. None
  85. None
  86. Thanks! @alcaeus github.com/alcaeus @doctrineproject