Doctrine

From Cloudrexx Development Wiki
Jump to: navigation, search

Introduction

This article describes the integration of Doctrine 2.0 in Cloudrexx.

The Doctrine Project is the home to several PHP libraries primarily focused on database storage and object mapping. The core projects are the Object Relational Mapper (ORM) and the Database Abstraction Layer (DBAL) it is built upon.

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 component) 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.

Important: 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.
Note: This documentation refers to version 5 or newer. For older versions of Cloudrexx, please refer to Contrexx V4 or Contrexx V3.

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

Entities are fetch through their repository which are fetched as follows:

$repo = $this->cx->getDb()->getEntityManager()->getRepository(
    'Cx\Core\ContentManager\Model\Entity\Page'
);
Important: For fetching a repository, you'll have to supply the FQCN of an entity like in the example above: Cx\Core\ContentManager\Model\Entity\Page

Then the entities can be fetched from the repository. I.e.:

$entity = $repo->find($id = 3);

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().

$myEntity->setVirtual(true);
Important: This feature is currently deprecated and should no longer be used.

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 Cloudrexx through its own Event System.

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

Example:

$evm = $this->cx->getEvents();
$myEntityEventListener = new \Cx\Modules\MyComponent\Model\Event\MyEntityEventListener();
$evm->addModelListener(
    \Doctrine\ORM\Events::prePersist,
    'Cx\\Modules\\MyModule\\Model\\Entity\\MyEntity',
    $myEntityEventListener
);

ENUM Data Type

There are some issues with using ENUM type columns with Doctrine. Please refer to Mysql Enums for details.

Cloudrexx prefers the second solution approach presented there: Solution 2: Defining a Type

Create a type definition in the Cx\Core\Model\Data\Enum\<component>\<class> namespace, name the file like CamelCasePropertyName.class.php, and put it into the corresponding subfolder of core/Model/Data/Enum/.

You can find examples in that same folder.

There's an exception to that rule regarding #Inheritance and discriminator columns.

Inheritance

Please refer to Inheritance Mapping for details.

You may use ENUM type discriminator columns, in which case there's no need to create a type definition as explained in #ENUM Data Type.

A working example can be found in core/DataSource/Model/Yaml/Cx.Core.DataSource.Model.Entity.DataSource.dcm.yml.

Doctrine CLI

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

$ cx wb db doctrine

Entity Validation

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.

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

If you perform a flush operation while any of the currently managed entities (by the Unit-of-Work) has an invalid property set, then an exception of class \Cx\Model\Base\ValidationException will be thrown.

try {
    $em = $this->cx->getDb()->getEntityManager();
    $entityManager->flush();
} catch (\Cx\Model\Base\ValidationException $ex ) {
    die("validation errrors: " . $ex->getMessage());
}

Debugging

Database

Doctrine is fully integrated into Cloudrexx'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:

\DBG::activate(DBG_DOCTRINE);

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, Cloudrexx's own function to dump variables, which is provided by Cloudrexx's Development tools, is able to handle Doctrine Entities. Use \DBG::dump() to dump a Doctrine Entity PHP object:

\DBG::dump($entity);

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 Cloudrexx

Packages

Currently integrated versions of Doctrine projects are as follows:

Package Namespace Project Version Location Requires Used by
doctrine/orm Doctrine\ORM Object Relational Mapper (ORM) 2.4.8 lib/doctrine/Doctrine/ORM
  • doctrine/collections~1.1
  • doctrine/dbal~2.4
  • symfony/console~2.0
  • symfony/yaml~2.1
  • \Cx\Core\Model\Entity\Db
  • Gedmo\Loggable
  • Gedmo\Tree
  • Gedmo\Translatable
  • Gedmo\Timestampable
  • Gedmo\Sluggable
  • beberlei\DoctrineExtensions
doctrine/dbal Doctrine\DBAL Database Abstraction Layer (DBAL) 2.4.5

2.12.1

lib/doctrine/vendor/doctrine-dbal/lib/Doctrine/DBAL
  • doctrine/common~2.4
  • doctrine/cache^1.0
  • doctrine/event-manager^1.0
  • doctrine/orm
doctrine/event-manager Doctrine\Common Event Manager 1.1.1

lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/

  • doctrine/dbal
doctrine/common Doctrine\Common Common 2.4.3 lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common
  • doctrine/inflector:1.*
  • doctrine/cache:1.*
  • doctrine/collections:1.*
  • doctrine/lexer:1.*
  • doctrine/annotations:1.*
  • Gedmo\Loggable
  • Gedmo\Tree
  • Gedmo\Translatable
  • Gedmo\Timestampable
  • Gedmo\Sluggable
doctrine/inflector Doctrine\Common\Inflector Inflector 1.1.0 lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/Inflector
  • doctrine/common
doctrine/cache Doctrine\Common\Cache Cache 1.4.4 lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/Cache
  • doctrine/common
  • doctrine/dbal
doctrine/collections Doctrine\Common\Collections Collections 1.3.0 lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/Collections
  • doctrine/orm
  • doctrine/common
doctrine/lexer Doctrine\Common\Lexer Lexer 1.0.1 lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/Lexer
  • doctrine/common
  • doctrine/annotations
doctrine/annotations Doctrine\Common\Annotations Annotations 1.2.7

lib/doctrine/vendor/doctrine-common/lib/Doctrine/Common/Annotations

  • doctrine/lexer:1.*
symfony/console Symfony\Component\Console Symfony Console 2.0.0 lib/doctrine/vendor/Symfony/Component/Console
  • doctrine/orm
symfony/finder Symfony\Component\Finder Symfony Finder 3.2.0 BETA1 lib/doctrine/vendor/Symfony/Component/Finder
symfony/yaml Symfony\Component\Yaml Symfony Yaml 2.6 lib/doctrine/vendor/Symfony/Component/Yaml

Extensions

The following Doctrine extensions have been integrated into Cloudrexx (in \Cx\Core\Model\Db::getEntityManager()):

Extension Description Version Location Requires Used by
Gedmo\Loggable helps tracking changes and history of objects, also supports version management 2.4.0 lib/doctrine/Gedmo/Loggable
  • doctrine/common~2.4
  • doctrine/orm^2.6.3
  • Cx\Core\Model\Entity\EntityBase
Gedmo\Tree this extension automates the tree handling process and adds some tree specific functions on repository.(closure, nestedset or materialized path) 2.4.0 lib/doctrine/Gedmo/Tree
  • doctrine/common~2.4
  • doctrine/orm^2.6.3
  • Cx\Core\ContentManager\Model\Entity\Node
Gedmo\Translatable adds localization support to entities 2.4.0 lib/doctrine/Gedmo/Translatable
  • doctrine/common~2.4
  • doctrine/orm^2.6.3
  • Cx\Core\Model\Entity\EntityBase
Gedmo\Timestampable updates date fields on create, update and even property change. 2.4.0 lib/doctrine/Gedmo/Timestampable
  • doctrine/common~2.4
  • doctrine/orm^2.6.3
  • Cx\Core\User
Gedmo\Sluggable urlizes your specified fields into single unique slug 2.4.0 lib/doctrine/Gedmo/Sluggable
  • doctrine/common~2.4
  • doctrine/orm^2.6.3
?
beberlei\DoctrineExtensions Adds support for MySQL/MariaDB functions like REGEXP, DATE_FORMAT etc. 1.2.3 lib/doctrine/beberlei/doctrineextensions
  • doctrine/orm^2.6
  • Cx\Core\User
DoctrineExtension\TablePrefixListener ensures Doctrine ORM uses the configured database prefix of the Cloudrexx installation. - model/extensions/DoctrineExtension/TablePrefixListener.php
  • Cx\Core\Model\Db

Customizings

This is a summary of the implemented customizings on the Doctrine libraries:

  • \Cx\Core\Model\Controller\YamlDriver (customizing of \Doctrine\ORM\Mapping\Driver\YamlDriver)
  • \Cx\Core\Model\Model\Event\LoggableListener (customizing of \Gedmo\Loggable\LoggableListener)
  • \Cx\Core\Model\Controller\EntityManager (customizing of \Doctrine\ORM\EntityManager)
  • \Cx\Core\Model\Model\Event\ORM (customizing of \Gedmo\Loggable\Mapping\Event\Adapter\ORM)
  • Added additional model property data types:

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 Cloudrexx 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 Cloudrexx.

class MyEntity extends \Cx\Model\Base\EntityBase {}

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 Cloudrexx 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.

class MyEntityRepository extends \Doctrine\ORM\EntityRepositor {}

Entity Manager

The Entity Manager can be retrieved as follows:

$em = $this->cx->getDb()->getEntityManager();

Mapping Driver

Cloudrexx uses a custom YAML mapping driver (\Cx\Core\Model\Controller\YamlDriver) that is based on the ORM's YAML mapping driver 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 Cloudrexx 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 Cloudrexx 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.

Automatic proxy file generation (see \Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()) is disabled in \Cx\Core\Model\Db::getEntityManager().

(Re)generate proxies using the following command:

$ cx wb db doctrine orm:generate-proxies

Caching

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

Class Autoloader

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

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->cx->getDb()->getEntityManager->getRepository(
    'Page'
);

Correct:

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

Reference Documentation

ORM
Doctrine
PHP