Doctrine in FLOW3

26
Karsten Dambekalns Persistence in FLOW3 with Doctrine 2 FLOW3 Experience 2012 1

description

Presentation given at F3X 2012 on the integration of Doctrine 2 into the PHP framework FLOW3.

Transcript of Doctrine in FLOW3

Page 1: Doctrine in FLOW3

Karsten Dambekalns

Persistence in FLOW3

with Doctrine 2

FLOW3 Experience 2012

1

Page 2: Doctrine in FLOW3

co-lead of TYPO3 5.0 and FLOW3

34 years old

lives in Lübeck, Germany

1 wife, 3 sons, 1 espresso machine

likes canoeing and climbing

Karsten Dambekalns

2

Page 3: Doctrine in FLOW3

Persistence in FLOW3 with Doctrine 2

Object Persistence in the Flow

• Based on Doctrine 2

• Seamless integration into FLOW3

• Provides the great Doctrine 2 features

• Uses UUIDs

• Our low-level persistence API

• Allows for own, custom persistence backends (instead of Doctrine 2)

• CouchDB is supported natively

3

Page 4: Doctrine in FLOW3

Basic Object Persistence

// Create a new customer and persist it: $customer = new Customer("Robert"); $this->customerRepository->add($customer);

// Update a customer: $customer->setName("I, Robot"); $this->customerRepository->update($customer);

// Find an existing customer: $otherCustomer = $this->customerRepository->findByFirstName("Karsten"); // … and delete it: $this->customerRepository->remove($otherCustomer);

• Get your repository injected conveniently

• Handle your objects (almost) like you had no framework

4

Page 5: Doctrine in FLOW3

Persistence in FLOW3 with Doctrine 2

Differences to plain Doctrine 2 in modeling

• Identifier properties are added transparently

• FLOW3 does autodetection for

• repository class names, column types, referenced column names

• target entity types, cascade attributes

• All Doctrine annotations work as usual

• Whatever you specify wins over automation

• Allows for full flexibility

5

Page 6: Doctrine in FLOW3

Purely Doctrine 2

use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity(repositoryClass="BugRepository") */class Bug {

/** * @var integer * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id;

/** * @var string * @ORM\Column(type="string") */ protected $description;

/** * @var \DateTime * @ORM\Column(type="datetime") */ protected $created;

/** * @var User * @ORM\ManyToOne(targetEntity="Example\User", inversedBy="assignedBugs") */ protected $engineer;

/** * @var \Doctrine\Common\Collections\Collection<Product> * @ORM\ManyToMany(targetEntity="Example\Product") */ protected $products;}

6

Page 7: Doctrine in FLOW3

use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity(repositoryClass="BugRepository") */class Bug {

/** * @var integer * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id;

/** * @var string * @ORM\Column(type="string") */ protected $description;

/** * @var \DateTime * @ORM\Column(type="datetime") */ protected $created;

/** * @var User * @ORM\ManyToOne(targetEntity="Example\User", inversedBy="assignedBugs") */ protected $engineer;

/** * @var \Doctrine\Common\Collections\Collection<Product> * @ORM\ManyToMany(targetEntity="Example\Product") */ protected $products;}

Doctrine 2 in FLOW3

7

Page 8: Doctrine in FLOW3

Purely Doctrine 2

/** * @var \DateTime * @ORM\Column(type="datetime") */ protected $created;

/** * @var Example\User * @ORM\ManyToOne(targetEntity="Example\User", inversedBy="assignedBugs") */ protected $engineer;

/** * @var \Doctrine\Common\Collections\Collection<Example\Product> * @ORM\ManyToMany(targetEntity="Example\Product") */ protected $products;}

8

Page 9: Doctrine in FLOW3

/** * @var \DateTime * @ORM\Column(type="datetime") */ protected $created;

/** * @var Example\User * @ORM\ManyToOne(targetEntity="Example\User", inversedBy="assignedBugs") */ protected $engineer;

/** * @var \Doctrine\Common\Collections\Collection<Example\Product> * @ORM\ManyToMany(targetEntity="Example\Product") */ protected $products;}

Doctrine 2 in FLOW3

9

Page 10: Doctrine in FLOW3

Using Repositories

Choose between the generic base repository to support any backend or the Doctrine base repository to access advanced Doctrine functionality.

Extending the base repositories of FLOW3

• Provides basic methods like:findAll(), countAll(), remove(), removeAll()

• Provides automatic finder methods to retrieve by property:findByPropertyName($value), findOneByPropertyName($value)

Add specialized finder methods to your own repository.

10

Page 11: Doctrine in FLOW3

Advanced Queries using the QOM

class PostRepository extends \FLOW3\Persistence\Repository {

/** * Finds most recent posts excluding the given post * * @param \TYPO3\Blog\Domain\Model\Post $post Post to exclude from result * @param integer $limit The number of posts to return at max * @return array All posts of the $post's blog except for $post */ public function findRecentExceptThis(\TYPO3\Blog\Domain\Model\Post $post, $limit = 20) { $query = $this->createQuery(); $posts = $query->matching($query->equals('blog', $post->getBlog())) ->setOrderings(array( 'date' => \TYPO3\FLOW3\Persistence\QueryInterface::ORDER_DESCENDING )) ->setLimit($limit) ->execute() ->toArray();

unset($posts[array_search($post, $posts)]); return $posts; }}

PostRepository.php

11

Page 12: Doctrine in FLOW3

Advanced Queries using DQL

class PostRepository extends \FLOW3\Persistence\Doctrine\Repository {

/** * Finds most recent posts excluding the given post * * @param \TYPO3\Blog\Domain\Model\Post $post Post to exclude from result * @param integer $limit The number of posts to return at max * @return array All posts of the $post's blog except for $post */ public function findRecentExceptThis(\TYPO3\Blog\Domain\Model\Post $post, $limit = 20) { // this is an alternative way of doing this when extending the Doctrine 2 // specific repository and using DQL. $query = $this->entityManager->createQuery('SELECT p FROM \TYPO3\Blog\Domain\Model\Post p WHERE p.blog = :blog AND NOT p = :excludedPost ORDER BY p.date DESC');

return $query ->setMaxResults($limit) ->execute(array('blog' => $post->getBlog(), 'excludedPost' => $post)); }}

PostRepository.php

12

Page 13: Doctrine in FLOW3

Modeling Associations

•Modeling associations is hard for many people

• Start with the model, not the data

• Read the Doctrine documentation on associations

• Put a printed list of possible association on your wall

• Always remember:

The owning side of a relationship determines the updates to the relationship in the database

13

Page 14: Doctrine in FLOW3

Modeling Associations

How FLOW3 helps you with associations

• Cascade attributes are managed by FLOW3

• based on aggregate boundaries

• Target entity can be left out

• Join columns and tables have automagic defaults

• No, not only if your identifier column is named id

• Check your mapping with flow3 doctrine:validate

All magic can be overridden by using annotations!

14

Page 15: Doctrine in FLOW3

Schema Management

Doctrine 2 Migrations

• Migrations allow schema versioning and change deployment

• Migrations are the recommended way for schema updates

• Can also be used to deploy predefined and update existing data

• Tools to create and deploy migrations are integrated with FLOW3

15

Page 16: Doctrine in FLOW3

Schema Management

Migrations Workflow

• Develop until your model is ready for a first “freeze”

• Create a migration and move / check / customize it

• Migrate to create the tables

$ ./flow3 doctrine:migrationgenerateGenerated new migration class!

Next Steps:- Move /…/DoctrineMigrations/Version20120328152041.php to YourPackage/Migrations/Mysql/- Review and adjust the generated migration.- (optional) execute the migration using ./flow3 doctrine:migrate

$ ./flow3 doctrine:migrate

16

Page 17: Doctrine in FLOW3

Schema Management

/** * Rename FLOW3 tables to follow FQCN */class Version20110824124835 extends AbstractMigration {

/** * @param Schema $schema * @return void */ public function up(Schema $schema) { $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql");

$this->addSql("RENAME TABLE flow3_policy_role TO typo3_flow3_security_policy_role"); $this->addSql("RENAME TABLE flow3_resource_resource TO typo3_flow3_resource_resource"); $this->addSql("RENAME TABLE flow3_resource_resourcepointer TO typo3_flow3_resource_resourcepointer"); $this->addSql("RENAME TABLE flow3_resource_securitypublishingconfiguration TO typo3_flow3_security_authorization_resource_securitypublis_6180a"); $this->addSql("RENAME TABLE flow3_security_account TO typo3_flow3_security_account"); }

Migration files are usually very simple code

17

Page 18: Doctrine in FLOW3

Schema Management

$ ./flow3 doctrine:migrationstatus

== Configuration >> Name: Doctrine Database Migrations >> Database Driver: pdo_mysql >> Database Name: blog >> Configuration Source: manually configured >> Version Table Name: flow3_doctrine_migrationstatus >> Migrations Namespace: TYPO3\FLOW3\Persistence\Doctrine\Migrations >> Migrations Directory: /…/Configuration/Doctrine/Migrations >> Current Version: 2011-06-08 07:43:24 (20110608074324) >> Latest Version: 2011-06-08 07:43:24 (20110608074324) >> Executed Migrations: 1 >> Available Migrations: 1 >> New Migrations: 0

== Migration Versions >> 2011-06-08 07:43:24 (20110608074324) migrated

Checking the migration status on the console

18

Page 19: Doctrine in FLOW3

Schema Management

Migrations Workflow

• Rinse and repeat: from now on create a new migration whenever you changed your model classes

• Generated migrations most probably need to be adjusted:

• Renaming a model means renaming a table, not dropping and creating

• Data migration might need to be added

• Sometimes the generated changes are useless

Good migrations make your user’s day

19

Page 20: Doctrine in FLOW3

Schema Management

Manual database updates

• For simple situations this can be good enough:

• Useful when

• You need to use an existing database dump

• No migrations exist for your database of choice (send patches!)

• Using SQLite (due to limited schema change functionality)

$ ./flow3 doctrine:create

$ ./flow3 doctrine:update

20

Page 21: Doctrine in FLOW3

Integrating existing database tables

Use existing data from TYPO3 or other applications

Two principal approaches

• Accessing raw data in a specialized repository

• Use your own database connection and SQL

• Does not use the default persistence layer

• Creating a clean model mapped to the existing structure

• FLOW3 will use the same database as the existing application

• Uses the default persistence layer

21

Page 22: Doctrine in FLOW3

Mapping fe_users to a model

/** * @FLOW3\Entity * @ORM\Table(name=”fe_users”) */class FrontendUser {

/** * @var integer * @ORM\Id * @ORM\Column(name="uid") * @ORM\GeneratedValue */ protected $identifier;

/** * @var string */ protected $username;

22

Page 23: Doctrine in FLOW3

Mapping fe_users to a model

/** * @var string */ protected $username;

/** * @var string * @ORM\Column(name="first_name") */ protected $firstName;

/** * @var \Doctrine\Common\Collections\Collection<My \Example\FrontendUserGroup> * @ORM\ManyToMany(mappedBy="users") * @ORM\JoinTable(name="user_groups_mm", …) */ protected $groups;

23

Page 24: Doctrine in FLOW3

Integrating existing database tables

Pitfalls

• Migrations will try to drop existing tables and columns!

• Data type mismatches break FK constraints

• integer vs. unsigned integer

• Real data can be bad data

• No FK constraints on legacy data

• Missing entries break associations

• Watch out for specifics like deleted and hidden flags

24

Page 25: Doctrine in FLOW3

Persistence in FLOW3 with Doctrine 2

Questions?

25