Doctrine

From Cloudrexx Development Wiki
Jump to: navigation, search

Introduction

This article describes the integration of Doctrine 2.0 in Contrexx.

Doctrine is an Object Relational Mapper (ORM) for PHP. If you are not familiar with Doctrine, it is advised to first go through the official Getting Started Guide of Doctrine or at least to read the articles Object-relational mapping and Doctrine (PHP) in Wikipedia.

The documentation in front of you is written with the intend to dive into the implementation of the model (of your own extension) right away, without having the need to go through extensive documentation in advance. However, be warned that if you encounter any issues during the implementation, resolving them might seem like an impossible task and will most likely force you to go through some of the external linked reference documentation anyway.

Note: To be able to follow this documentation you will need a basic understanding of OOP and Namespaces in PHP (5.3+). See reference documentation below for further tutorials and documentation.

Basic Terminology

To understand the following document we have to introduce some of the used terminology by Doctrine or by the concept of an ORM in general.

Entity
  • Every PHP object that you want to save in the database using Doctrine is called an Entity.
  • The term Entity describes objects that have an identity over many independent requests. This identity is usually achieved by assigning a unique identifier to an entity.
Entity Repository
  • A Entity Repository can be seen as a container of all existing Entities of a specific class.
Entity Manager
  • As said by its name, the Entity Manager is used to manage the Doctrine Entities.
  • The EntityManager Class is a central access point to the ORM functionality provided by Doctrine 2.
  • The EntityManager API is used to manage the persistence of your objects (Entities) and to query for persistent objects (Entities).
  • The Entity Manager uses a UnitOfWork to keep track of all the things that need to be done the next time flush is invoked.
Unit of Work
  • The UnitOfWork can be seen as a repository that contains all loaded Doctrine Entities that are managed by the Doctrine Entity Manager
Entity State
  • An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED. See reference documentation to Entity States.
Entity Persistence
  • Not all attributes of a Doctrine Entity are automatically persisted to the database. Instead each attribute must explicitly be marked as such to get it persisted into the database.
Persist Operation
  • Instructing the Entity Manager to start managing an unmanaged Doctrine Entity and add it to the database on the next Flush Operation.
Flush Operation
  • Writing all changes made to the Entities managed by the Entity Manager (and hold by the UnitOfWork of the Entity Manager) to the database
Mapping
  • The mapping definition between an Entity in PHP and its counterpart as a dataset in the database.
  • The definition to map a PHP class variable to a database field
Association
  • The relationship between two Entities (One-To-One / One-To-Many / Many-To-Many / Many-To-One).
  • In a relationship, one Entity is always the Owning Side and one is the Inverse Side. See article Associations to get a full understanding of this concept. It is essential to fully understand the concept of the Owning and Inverse Side to be able to successfully set up an association between two Entities.

Getting Started Tutorial

Basic Mapping

Basic Mapping


YAML Notation

If unfamiliar with YAML, it is advised to at least go through the sections Examples and Syntax of the YAML article of Wikipedia to get a basic understanding of YAML.


A comprehensive example of a YAML mapping document can be found in the official documentation of Doctrine.

Mapping document

Entity definition (MUST)
<fully qualified name of the class>:
  type: entity
  repositoryClass: <fully qualified name of the repository class>
  table: <database name, without table prefix>
  fields:
    <fields definition>
Index definition (optional)
Field definition (MUST)
Association definition (optional)


Fetching Entities

Hint: to get repositories, you have to provide the whole path, example

$entityManager->getRepository('Cx\Model\ContentManager\Page');

, otherwise the entity won't be found.

Advanced Topics

Virtual Entities

A Doctrine Entity can be flagged as virtual to instruct the Entity Manager to not flush any changes made on the Entity to the database when the Flush Operation is performed. This is implemented by the method Cx\Model\Base\EntityBase::setVirtual().

<highlightsyntax>$myEntity->setVirtual(true);</highlightsyntax>

Events

During the lifecycle of an Entity certain events are triggered to which custom operations can be attached to. The Event System of Doctrine is integrated in Contrexx through its own Event System.

To each of the available Lifecycle Events of an Entity, an Event Listener can be registered.

Example: <highlightsyntax>$evm = \Env::get('cx')->getEvents(); $myEntityEventListener = new \Cx\Modules\MyModule\Model\Event\MyEntityEventListener(); $evm->addModelListener(\Doctrine\ORM\Events::prePersist, 'Cx\\Modules\\MyModule\\Model\\Entity\\MyEntity', $myEntityEventListener);</highlightsyntax>

Doctrine CLI

The command line interface of Doctrine is accessible through the Workbench:

$ ./workbench db doctrine

Entity Validation

Note: This section is not accurate anymore! A so called automatic entity validation mechanism as described below is not available at the moment.

A Doctrine Entity can automatically be validated on the Persist and the Flush Operation. This can be achieved by mapping specific validation callables to specific attributes of an Entity. The validation mapping is to be set on the member variable $validators.

<highlightsyntax>$this->validators = array(

   'integerField' => new \CxValidateInteger(),
   'textField' => new \CxValidateString(array('alphanumeric' => true, 'maxlength' => 255)),
   'textField' => new \CxValidateRegexp(array('pattern' => '/^[-A-Za-z0-9_]+$/')),            

);</highlightsyntax>

If you want to save a new or an existing entity via $entityManager->persist($entity), a ValidationException will be thrown. <highlightsyntax>try {

   $entityManager->persist($entity);
   $entityManager->flush();

} catch ( \Cx\Model\Base\ValidationException $ex ) {

   die("validation errrors: " . $ex->getMessage());

}</highlightsyntax>

Debugging

Database

Doctrine is fully integrated into Contrexx's Development tools. When activating database-logging (DBG_DB), the effective SQL-queries made by Doctrine are being logged along with the regular AdoDB SQL-queries. To explicitly only log Doctrine's SQL-queries the mode DBG_DOCTRINE can be used: <highlightsyntax>\DBG::activate(DBG_DOCTRINE);</highlightsyntax>

See section Debug Levels for additional, Doctrine specific, log levels.

Note: Internally, DBG does register Doctrine\DBAL\Logging\EchoSQLLogger() as an Doctrine\DBAL\Logging\SQLLogger on Doctrine.

Entities

Doctrine Entities are complex PHP objects which do most of the time contain complex and recursive references. Therefore, PHP native's functions to dump variables like print_r(), var_dump() or var_export() are not ideal when using in conjunction with Doctrine Entities. However, Contrexx's own function to dump variables, which is provided by Contrexx's Development tools, is able to handle Doctrine Entities. Use \DBG::dump() to dump a Doctrine Entity PHP object:

<highlightsyntax>\DBG::dump($entity);</highlightsyntax>

Testing

This section is outdated and not accurate anylonger and must be rewritten!

The tests are located in testing/tests/model.

It is highly recommended to write tests for each entity and repository: If something has been changed in the Model, it is used to check whether everything still works and whether you get the expected results. So you don't have to test manually the whole module.

There is a special test case testing/testCases/DoctrineTestCase.php, your test case should inherit from. This test case provides the entity manager and surrounds each test with an single transaction. So there will not be any change done to the testing database.

Example in: testing/tests/model/contentManager/PageRepositoryTest.php

Integration in Contrexx

Initialization / Configuration

Doctrine gets initialized and configured when calling the method Cx\Core\Model\Db::getEntityManager() the first time. The latter is also used to fetch the Doctrine Entity Manager. See section Entity Manager below on how to properly fetch the Entity Manager.

Entity Integration

All Entity Models must be located in their component's folder Model/Entity.

The file name and namespace of a model must follow the Contrexx naming conventions.

All Doctrine Entities must extend from the base class Cx\Model\Base\EntityBase. This is required to ensure that each Entity can be properly handled by all components of Contrexx.

<highlightsyntax>class MyEntity extends \Cx\Model\Base\EntityBase {}</highlightsyntax>

Entity Repository Integration

All Entity Repositories must be located in their component's folder Model/Repository.

The file name and namespace of a repository must follow the Contrexx naming conventions. Additionally, the file name must contain the suffix Repository. I.e.: MyEntityRepository

All Doctrine Entity Repositories must extend from the base class Doctrine\ORM\EntityRepository.

<highlightsyntax>class MyEntityRepository extends \Doctrine\ORM\EntityRepositor {}</highlightsyntax>

Entity Manager

The Entity Manager can be retrieved as follows: <highlightsyntax>$em = \Cx\Core\Core\Controller\Cx::instanciate()->getDb()->getEntityManager();</highlightsyntax>

However, to comply with the testing methodology, the access to the Entity Manager should be done through a local class variable reference to \Cx\Core\Core\Controller\Cx that was set on component initialization where the current instance of \Cx\Core\Core\Controller\Cx is being passed as an argument. See section Dependency Injection about further information about this practice.

Mapping Driver

Contrexx uses the YAML mapping driver of Doctrine to describe/specify the ORM metadata. Each Doctrine entity is described by a single YAML mapping document.

A YAML mapping document must comply to the following conventions to be valid:

  • It must describe exactly one single entity (multiple entities can't be defined in the same document)
  • The document must be located in the folder Model/Yaml of the related Contrexx component
  • The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.).
  • All mapping documents must have the extension ”.dcm.yml” to identify it as a Doctrine mapping file.

The location of each component's Yaml directory is automatically registered by the System Component Repository (Cx\Core\Core\Model\Repository\SystemComponentRepository) when initializing all available Contrexx Components. The registration is done using the method \Cx\Core\Model\Db::addSchemaFileDirectories().

Exception for Gedmo models

The models of the Gedmo extensions (see section Extensions) are loaded using the Annotation mapping driver. Therefore, be aware that in case you are going to extend any of the Gedmo models, you'll have to use annotations in such case. However, this does not affect the model mapping when using any of the Gedmo extensions. Meaning that you would still be defining the mapping data in the regular YAML mapping document of the Doctrine Entity that shall be using a specific Gedmo extension.

Proxies

All entity proxies are located under /model/proxies in the file system and are located within the namespace Cx\Model\Proxies.

If the Workbench configuration file (workbench.config) is present, then the proxy files are generated/updated automatically (\Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()).

Caching

The configured Database cache engine (Administration > Global Configuration Caching) of the Caching System is used as the Result and Query Cache of Doctrine. As for the Metadata Cache, Doctrine's ArrayCache is used.

Class Autoloader

All Doctrine library classes and the Entity model classes as well as their Repositories classes are loaded through the ClassLoader of Contrexx.

Extensions

The following Doctrine extensions have been integrated into Contrexx.

In use
  • Gedmo\Loggable - helps tracking changes and history of objects, also supports version management.
  • Gedmo\Tree - this extension automates the tree handling process and adds some tree specific functions on repository. (closure, nestedset or materialized path)
Not in use, nor have the following extensions been tested
  • Gedmo\Sluggable - urlizes your specified fields into single unique slug
  • Gedmo\Timestampable - updates date fields on create, update and even property change.
  • Gedmo\Translatable - gives you a very handy solution for translating records into different languages. Easy to setup, easier to use.

Documentation on the above listed extensions can be found in the related documentation or on the Maintainer's blog.

Common mistakes

Fatal error: Uncaught exception 'ReflectionException' with message 'Class Page does not exist' in ...
The error is: no Namespace have been added to the parameter

$this->pageRepo = $this->em->getRepository('Page');

Correct:

$this->pageRepo = $this->em->getRepository('Cx\Model\ContentManager\Page');

Reference Documentation

ORM
Doctrine
PHP