An introduction to Doctrine ORM focussing on how to set up the ORM, configure entities and associations and an overview of other available features like DQL, lifecycle event or migrations.
that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication. (http://www.doctrine-project.org/projects.html) 4
several PHP libraries primarily focused on database storage and object mapping. The core projects are a Object Relational Mapper (ORM) and the Database Abstraction Layer (DBAL) it is built upon. Doctrine has greatly benefited from concepts of the Hibernate ORM and has adapted them to fit the PHP language. (http://www.doctrine-project.org/index.html) • Around since 2006 with very stable, high-quality codebase • Support for both high-level and low-level database programming for all your use-cases • Large community and integrations with many different frameworks (Zend Framework, Symfony and more) 5
ORM use the package doctrine/orm • The current version is 2.5 • For Symfony support use the package doctrine/doctrine-bundle • The current version is 1.6 • For Doctrine migrations use the package doctrine/doctrine-migrations-bundle • The current version is 1.2 7
logging/profiling, slaves, custom types and more • If in doubt, use the configuration reference: https://symfony.com/doc/current/reference/ configuration/doctrine.html 9
mappings, DQL functions, proxy classes and more • Each entity manager requires a DBAL connection • If in doubt, use the configuration reference: https://symfony.com/doc/current/reference/ configuration/doctrine.html 10
an entity. Entities have special modelling and design considerations. They have life cycles that can radically change their form and content, but a thread of continuity must be maintained. Their identities must be defined so that they can be effectively tracked. Their class definitions, responsibilities, attributes and associations should evolve around who they are, rather than the particular attributes they carry. (Eric Evans, Domain-Driven Design - Tackling Complexity in the Heart of Software) 11
Describing the structure using mapping metadata to determine how an entity should be stored in the database • For CRUD/REST services, annotation are sufficient • For rich domain models, XML/YAML is recommended • If in doubt, use the annotation reference: http://docs.doctrine-project.org/projects/doctrine-orm/en/ latest/reference/annotations-reference.html 12
with Doctrine ORM • Properties: • repositoryClass: FQCN of a custom repository class, recommended • readOnly: no change-tracking, persisting/removing still works, defaults to false 13
table where it is persisted • Properties: • name: the name of the table, defaults to the class name • indexes: a list of indexes, defaults to empty • uniqueConstraints: a list of unique indexes, defaults to empty • schema: the name of the schema the table lives in 14
is used to create primary keys • Possible strategies are AUTO (MySQL, SQLite), SEQUENCE (Oracle, PostgreSQL), UUID, NONE and more • Properties: • strategy: the name of the strategy, defaults to AUTO 16
column in a database table • Properties: • type: the data type, defaults to string • name: the column name, defaults to the property name • unique: whether the column values are unique, defaults to false • nullable: whether the column values are NOT NULL, default to false • options: a list of additional options (DEFAULT, UNSIGNED, COLLATE) 17
entities • Can be uni-directional or bi-directional • Bi-directional associations have an owning and an inverse side, uni-directional associations only have an owning side • Doctrine only checks the owning side for changes • Collections of entities have to be instances of Doctrine\Common\Collections\Collection 18
one other entity • Can also be self-referencing • Properties: • targetEntity: the other reference entity • mappedBy: the owning side of the association • inversedBy: the inverse side of the association • cascade: which operations (persist, remove and more) should be cascaded to referenced entities 19
other entities • Represents the inverse side of the association • Can also be self-referencing • Properties: • targetEntity: the other reference entity • mappedBy: the owning side of the association • cascade: which operations (persist, remove and more) should be cascaded to referenced entities 20
• Represents the owning side of the association • Properties: • targetEntity: the other reference entity • cascade: which operations (persist, remove and more) should be cascaded to referenced entities 21
other entities • Requires a join table containing the primary keys from each side of the association • Can also be self-referencing • Properties: • targetEntity: the other reference entity • mappedBy: the owning side of the association • inversedBy: the inverse side of the association • cascade: which operations (persist, remove and more) should be cascaded to referenced entities 22
by numerical keys starting with 0 • DQL offers the INDEX BY keyword to index collections • The indexBy property lets you define indexes implicitly • Only use this feature with primary keys or columns with unique values 23
the fetch property to change this behaviour • Use EAGER to always load associations together with the entity • Can be slow/costly for huge collections, affects hydration performance • Use EXTRA_LAZY to optimise the performance for huge collections • Certain collection methods (contains, count, get, slice and more) trigger single SELECT statements instead of fully loading the collection • Once the collection is fully loaded, the default behaviour is used again 24
referenced entities to enable lazy-loading functionality • Proxies extend the original entity class and implement Doctrine\ORM\Proxy\Proxy • Use Doctrine\Common\Util\ClassUtils to gather information about underlying classes • Be careful with using var_dump(), print_r() and others on such an object graph • Automatically generated in the development environment • In production, use bin/doctrine orm:generate-proxies 25
using an identity map • Methods like persist() or remove() only tell Doctrine to track entities/changes • Only after calling flush() are changes executed against the database • Usually, Doctrine wraps all changes in a single transaction • Changes are executed in the correct order taking foreign-key constraints into consideration • Support for explicit transaction handling is available (explicit boundaries, closure-wrapping) • Support for optimistic and pessimistic locking is available 26
as a conceptual set. It acts like a collection, except with more elaborate querying ability. Objects of the appropriate type are added and removed, and the machinery behind the repository inserts them or deletes them from the database. This definition gathers a cohesive set of responsibilities for providing access to the roots of aggregates from early life cycle through the end. (Eric Evans, Domain-Driven Design - Tackling Complexity in the Heart of Software) 27
method • They offer generics methods like find(), findAll(), findBy() and more to retrieve entities • Custom repositories can be created using the repositoryClass property of the @Entity annotation • Classes should extend Doctrine\ORM\EntityRepository • Use factories to make them available in the DIC • Injection of a Doctrine\Common\Persistence\ManagerRegistry is also possible • No inheritance needed 28
over your object model • Supports SELECT, UPDATE and DELETE queries • Supports JOINs • Supports partial hydration and different hydration modes (HYDRATE_OBJECT, HYDRATE_ARRAY, HYDRATE_SCALAR and more) • DQL SELECT queries allow you to fetch parts that are otherwise not accessible (partials, aggregates and more) or to fetch entities and their associations in a more performant manner • DQL UPDATE/DELETE queries allow you to execute bulk changes 29
• Should be used in favour of writing string-based queries • Provides methods for statements like select(), from(), join(), where(), orderBy(), groupBy() and more • Allows for complex WHERE clauses using the expr() helper methods (orX(), andX(), eq(), lt(), gt() and more) • Allows for parameter binding using numeric placeholders (?1, ?2) and named placeholders (:id, :name) 30
trigger a bunch of global and lifecycle events • Examples are prePersist/postPersist, preUpdate/postUpdate, preFlush/onFlush/postFlush and more • Entities have to be annotated with @HasLifecycleCallbacks • Each callback method has to be annotated with @PrePersist/@PostPersist etc. • Event arguments are available to access the entity manager and the unit of work • Entities can also be annotated with @EntityListeners • Each callback receives the entity and the event arguments as arguments 31
• Second level cache (experimental) • Multiple backends available in the Doctrine Cache library (Memcache, Memcached, Redis, XCache and more) • Use ArrayCache for debugging/testing 32
and easily deploy changes to it • Changes are implemented as an ordered list of version files • Each version file must be named like Version.{1,255}\.php and contain up() and down() methods • Use bin/console doctrine:migrations:status to show the current migration status • Use bin/console doctrine:migrations:migrate to run migrations • Optional argument version, defaults to the latest version • Use aliases first, prev, next and latest to easily jump between versions 35