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

Doctrine - more than an ORM (Symfony User Group...

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

Avatar for alcaeus

alcaeus

May 09, 2019
Tweet

More Decks by alcaeus

Other Decks in Programming

Transcript

  1. 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' )); } }
  2. $ 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
  3. $ 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]: >
  4. class Email { private $id; private $email; public function __construct(string

    $email) { $this->email = $email; } public function getEmail(): string { return $this->email; } }
  5. /** * @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); } }
  6. 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 {}
  7. /** * @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; }
  8. 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; }
  9. use App\Annotation\SuperDuperClass; use Doctrine\ORM\Mapping as ORM; /** * @SuperDuperClass(level=5) *

    @ORM\Entity(repositoryClass=EmailRepository::class) */ class Email
  10. 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; }
  11. /** * @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); } }
  12. /** * 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;
  13. $ ./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
  14. 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'); } }
  15. $ ./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
  16. 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)%'
  17. 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"; }
  18. - 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
  19. - 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
  20. /** * @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); }
  21. /** * @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; }
  22. interface Collection extends Countable, IteratorAggregate, ArrayAccess { public function add($element);

    public function clear(); public function contains($element); public function isEmpty(); public function remove($key); // ... }
  23. 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; } ); }
  24. class UserRepository extends ServiceEntityRepository { public function findOneByUsername(string $username): ?User

    { return $this->createQueryBuilder('u') ->andWhere('u.username = :username') ->setParameter('username', $username) ->getQuery() ->getOneOrNullResult() ; } }
  25. 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() ; } }
  26. 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 ]; }
  27. 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
  28. 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
  29. 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
  30. 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);
  31. class Dummy#4 (1) { private $foo => NULL } Uncaught

    TypeError: Return value of Dummy::getFoo() must be of the type string, null returned
  32. $ 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
  33. class User { // ... /** * @ORM\Column(type="datetime") * @Gedmo\Timestampable(on="create")

    */ private $createdAt; /** * @ORM\Column(type="datetime") * @Gedmo\Timestampable(on=“update") */ private $updatedAt; }
  34. 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'; }
  35. - 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
  36. - 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
  37. $ 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)
  38. /** * @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; } }
  39. $ 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
  40. final class Email { private $email; public function setEmail(string $email)

    : void { $this->email = $email; } public function getEmail() : ?string { return $this->email; } }
  41. <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>