Getting started with Doctrine2

Doctrine2 Installation

Define the following requirement in your composer.json file:

{
    "require": {
        "doctrine/orm": "*"
    }
}

Then call composer install from your command line. For more details consult Doctrine2 documentation or Composer documentation.

Doctrine2 configuration

Class loading

Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project:

<?php
// bootstrap.php
// Include Composer Autoload (relative to project root).
require_once "vendor/autoload.php";

For more details check the documentation.

Entity Manager

Once you have prepared the class loading, you acquire an EntityManager instance. The EntityManager class is the primary access point to ORM functionality provided by Doctrine.

Annotations
XML
YML
More info
Links
<?php
// bootstrap.php
require_once "vendor/autoload.php";

use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;

$paths = array("/path/to/entity-files");
$isDevMode = false;

// the connection configuration
$dbParams = array(
    'driver'   => 'pdo_mysql',
    'user'     => 'root',
    'password' => '',
    'dbname'   => 'foo',
);

$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
<?php
$paths = array("/path/to/xml-mappings");
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
<?php
$paths = array("/path/to/yml-mappings");
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);

Inside the Setup methods several assumptions are made:

If $isDevMode is true caching is done in memory with the ArrayCache. Proxy objects are recreated on every request.

If $isDevMode is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless $cache is passed as fourth argument.

If $isDevMode is false, set then proxy classes have to be explicitly created through the command line.

If third argument $proxyDir is not set, use the systems temporary directory.

Doctrine 2 entities and association on the enhanced ER diagram automatically generated by Skipper

Generated by Skipper

Command line tool

You need to register your applications EntityManager to the console tool to make use of the tasks by creating a cli-config.php file with the following content:

version 2.4 and newer
version 2.3 and older
<?php
use Doctrine\ORM\Tools\Console\ConsoleRunner;

// replace with file to your own project bootstrap
require_once 'bootstrap.php';

// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();

return ConsoleRunner::createHelperSet($entityManager);
<?php
// cli-config.php
require_once 'my_bootstrap.php';

// Any way to access the EntityManager from  your application
$em = GetMyEntityManager();

$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
    'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
    'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

For more details check the documentation.

Doctrine2 basic use

Generating Doctrine2 model from database

$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml

For more details check the documentation.

Creating database tables from Doctrine2 model

Generate the database schema:

$ php doctrine orm:schema-tool:create

Update the database schema:

$ php doctrine orm:schema-tool:update

For more details check the documentation.

Using Doctrine2 with Symfony2

Installation

Add required bundles to composer.json:

    "require": {
        ....
        ....
        "doctrine/orm": "*",
        "doctrine/doctrine-bundle": "*",
    },

And enable the bundles in the Kernel:

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            .......
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
        );

        .....

        return $bundles;
    }
}

Configuration

Configure app/config/config.yml and add Doctrine section:

doctrine:
    dbal:
        driver:   %database_driver%
        host:     %database_host%
        port:     %database_port%
        dbname:   %database_name%
        user:     %database_user%
        password: %database_password%
        charset:  UTF8

    orm:
        auto_generate_proxy_classes: %kernel.debug%
        auto_mapping: true

Configure app/config/parameters.yml and set database connection parameters:

parameters:
    database_driver:   pdo_mysql
    database_host:     127.0.0.1
    database_port:     ~
    database_name:     symfony
    database_user:     UsernameHere
    database_password: PasswordHere

Configure your schema

You can use annotations for schema definition. Entities can be stored at src\Acme\SampleBundle\Entity\User.php:

<?php
namespace Acme\SampleBundle\Entity;
use Doctrine\ORM\Mapping AS ORM;

/** 
 * @ORM\Entity
 * @ORM\Table(name="acme_user")
 */
class Event
{
    /** 
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /** 
     * @ORM\Column(type="string", unique=true, length=64, nullable=false)
     */
    private $name;
}

Getters and setters can be generated simply by running:

php app/console doctrine:generate:entities Acme

Using Doctrine2 with Symfony1.4

Installation

First we need to install the plugin from SVN with the following command from the root of your project:

$ svn co http://svn.symfony-project.org/plugins/sfDoctrinePlugin/branches/1.3-2.0/ plugins/sfDoctrine2Plugin

Now you just need to enable the plugin:

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->enablePlugins('sfDoctrine2Plugin');
  }
}

Configuration

Configure config/databases.yml for database connection:

all:
  doctrine:
    class: sfDoctrineDatabase
    param:
      options:
        driver: pdo_mysql
        user: root
        password:
        dbname: doctrine

Configure Your Schema

Below is an example of a simple User entity:

# config/doctrine/schema.yml
 
Models\User:
  type: entity
  table: user
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    username:
      type: string
      length: 255
    password:
      type: string
      length: 255

Basic use

Writing Data Fixtures

The times of using YAML for data fixtures is no longer. Instead, you are only required to use plain PHP for loading your data fixtures.

// data/fixtures/fixtures.php
 
$em = $this->getEntityManager();
 
$admin = new \Models\User();
$admin->username = 'admin';
$admin->password = 'changeme';

Building Doctrine

Now you’re ready to build everything. The following command will build models, forms, filters, database and load data fixtures.

$ php symfony doctrine:build --all --and-load

Updating Schema

If you change your schema mapping information and want to update the database you can easily do so by running the following command after changing your mapping information.

$ php symfony doctrine:build --all-classes --and-update-schema

Using Doctrine2 with Zend Framework 2

Installation

php composer.phar require doctrine/doctrine-orm-module:0.7.*
php composer.phar require zendframework/zend-developer-tools:dev-master
cp vendor/zendframework/zend-developer-tools/config/zenddevelopertools.local.php.dist config/autoload/zdt.local.php

Enable the modules in config/application.config.php:

return array(
    'modules' => array(
        'ZendDeveloperTools',
        'DoctrineModule',
        'DoctrineORMModule',
        'Application',
    ),
    // [...]
);

Doctrine2 Model Elements

Schema file structure

Doctrine2 uses one *.dcm.xml schema file for each entity. The file is structured like this:

Annotations
XML
YML
Links
<?php
use Doctrine\ORM\Mapping AS ORM;

/** 
 * @ORM\Entity(repositoryClass="Doctrine\ORM\EntityRepository")
 * @ORM\Table
 */
class itemRecord
{
    /** 
     * @ORM\Id
     */
    private $id;

    /** 
     * @ORM\Column
     */
    private $name;
<?xml version="1.0"?>
<doctrine-mapping 
xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" 
xsi="http://www.w3.org/2001/XMLSchema-instance" 
schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

  <entity name="itemRecord"/>

</doctrine-mapping>
itemRecord:
  type: entity
  fields:
  indexes:
  oneToOne:
  oneToMany:
  manyToOne:
  manyToMany:
  discriminatorColumn:
  discriminatorMap:
Doctrine 2 schema as ER diagram automatically generated by Skipper.

Generated by Skipper

You can go and see how to do this in few clicks.

Entity

Simple entity

Simple entity with a primary key and several fields:

Annotations
XML
YML
Links
<?php
use Doctrine\ORM\Mapping AS ORM;

/**
 * @ORM\Entity
 */
class author
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=false)
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", nullable=false)
     */
    private $lastName;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    private $birthDate;
<entity name="author">
  <id name="id" type="integer">
    <generator strategy="AUTO"/>
  </id>
  <field name="firstName" type="string" nullable="false"/>
  <field name="lastName" type="string" nullable="false"/>
  <field name="birthDate" type="string" nullable="true"/>
</entity>
author:
  type: entity
  fields:
    id:
      id: true
      type: integer
      generator:
        strategy: AUTO
    firstName:
      type: string
      nullable: false
    lastName:
      type: string
      nullable: false
    birthDate:
      type: string
      nullable: true
Doctrine 2 entity displayed in Skipper ER diagram.

Generated by Skipper

Entity with all options defined

Entity with all options defined:

Annotations
XML
YML
Links
<?php
use Doctrine\ORM\Mapping AS ORM;

/**
 * 
 * @ORM\Table(
 *     schema="item_record",
 *     name="item_record",
 *     options={
 *         "charset":"utf8",
 *         "collate":"utf8_unicode_ci",
 *         "comment":"library record of a work",
 *         "temporary":false,
 *         "engine":"InnoDB"
 *     },
 *     indexes={
 *         @ORM\Index(name="ix_name", columns={"itemRecord_name"}),
 *         @ORM\Index(name="ix_name_publisher", columns={"itemRecord_publisherId","itemRecord_name"})
 *     },
 *     uniqueConstraints={
 *         @ORM\UniqueConstraint(name="ix_name_ean", columns={"itemRecord_name","eanId"}),
 *         @ORM\UniqueConstraint(name="ix_ean_publisher", columns={"itemRecord_publisherId","eanId"})
 *     }
 * )
 * @ORM\DiscriminatorMap(
 *     {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
 * )
 * @ORM\DiscriminatorColumn(name="item", type="string")
 * @ORM\InheritanceType("JOINED")
 * 
 * 
 * 
 * @ORM\HasLifecycleCallbacks
 * @ORM\ChangeTrackingPolicy("DEFERRED_IMPLICIT")
 * @ORM\Entity(repositoryClass="Doctrine\ORM\EntityRepository")
 */
class itemRecord
{
    /**
     * @ORM\Id
     * @ORM\Column()
     */
    private $id;

    /**
     * @ORM\Column()
     */
    private $name;

    /**
     * @ORM\OneToOne()
     */
    private $ean;

    /**
     * @ORM\OneToMany()
     */
    private $physicalCopy;

    /**
     * @ORM\ManyToOne()
     * @ORM\JoinColumn()
     */
    private $publisher;

    /**
     * @ORM\ManyToMany()
     */
    private $author;

    /**
     * @ORM\PostPersist
     */
    public function sendOptinMail()
    {
    }
}
<entity name="library\itemRecord" 
inheritance-type="JOINED" 
change-tracking-policy="DEFERRED_IMPLICIT" 
repository-class="Doctrine\ORM\EntityRepository" 
schema="item_record" table="item_record">
    <id name="id"/>
    <field name="name"/>
    <indexes/>
    <unique-constraints/>
    <lifecycle-callbacks/>
    <many-to-one field="publisher"/>
    <one-to-one field="ean"/>
    <one-to-many field="physicalCopy"/>
    <many-to-many field="author"/>
    <discriminator-column name="item"/>
    <discriminator-map/>
    <options>
      <option name="charset" value="utf8"/>
      <option name="collate" value="utf8_unicode_ci"/>
      <option name="comment" value="library record of a work"/>
      <option name="temporary" value="false"/>
      <option name="engine" value="InnoDB"/>
    </options>
  </entity>
</doctrine-mapping>
library\itemRecord:
  type: entity
  inheritanceType: JOINED
  changeTrackingPolicy: DEFERRED_IMPLICIT
  repositoryClass: Doctrine\ORM\EntityRepository
  schema: item_record
  table: item_record
  fields:
  indexes:
  uniqueConstraints:
  lifecycleCallbacks:
  options:
    charset: utf8
    collate: utf8_unicode_ci
    comment: library record of a work
    temporary: false
    engine: InnoDB
  oneToOne:
  oneToMany:
  manyToOne:
  manyToMany:
  discriminatorColumn:
  discriminatorMap:
Doctrine 2 entity and its association shown is Skipper visual editor.

Generated by Skipper

You can go and see how to do this in few clicks.

Id

Primary key definition:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\Id
     * @ORM\Column(
     *     type="integer",
     *     name="itemRecord_id"
     *     length=255     
     * )
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
<id name="id" type="integer" length="255">
  <generator strategy="AUTO"/>
</id>
  fields:
    id:
      id: true
      type: integer
      length: 255
Doctrine 2 entity imported to Skipper visual model from schema definitions.

Generated by Skipper

Primary key with all base properties set:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\Id
     * @ORM\Column(
     *     type="integer",
     *     name="itemRecord_id",
     *     length=255
     *     columnDefinition="itemRecord_id",
     *     precision=3,
     *     scale=3,
     *     options={"unsigned":true,"comment":"this is primary key","version":2}
     * )
     * @ORM\Version
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     */
    private $id;
<id name="id" type="integer" length="255" column="itemRecord_id" 
column-definition="itemRecord_id" precision="3" scale="3" version="true">
  <generator strategy="SEQUENCE"/>
  <sequence-generator 
  allocation-size="1" 
  initial-value="1" 
  sequence-name="itemRecord_id_seq"/>
  <options>
    <option name="unsigned" value="true"/>
    <option name="comment" value="this is primary key"/>
    <option name="version" value="2"/>
  </options>
</id>
itemRecord:
  type: entity
  fields:
    id:
      id: true
      type: integer
      length: 255
      column: itemRecord_id
      columnDefinition: itemRecord_id
      precision: 3
      scale: 3
      version: true
      generator:
        strategy: SEQUENCE
      sequence-generator:
        allocationSize: 1
        initialValue: 1
        sequenceName: itemRecord_id_seq
      options:
        unsigned: true
        comment: this is primary key
        version: 2
Doctrine 2 entity imported to Skipper visual model from schema definitions.

Generated by Skipper

You can go and see how to do this in few clicks.

Field

Regular field definition:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\Column(
     *     type="string",
     *     unique=true,
     *     length=255
     * )
     */
    private $name;
<field name="name" type="string" length="255" nullable="true"/>
    name:
      type: string
      unique: true
      length: 255
Doctrine 2 entity and selected field in Skipper ER diagram.

Generated by Skipper

Regular field with all options set:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\Column(
     *     type="string",
     *     length=255,
     *     nullable=false,
     *     name="itemRecord_name",
     *     columnDefinition="itemRecord_name",
     *     precision=1,
     *     scale=1,
     *     options={"comment":"this is field","unsigned":true,"version":3}
     * )
     */
    private $item;
}    
<field name="item" type="string" length="255" nullable="false" column="itemRecord_name" column-definition="itemRecord_name" precision="1" scale="1" version="false">
  <generator>
    <strategy>UUID</strategy>
  </generator>
  <sequence-generator>
    <allocation-size>2</allocation-size>
    <initial-value>1</initial-value>
    <sequence-name>itemRecord_name_seq</sequence-name>
  </sequence-generator>
  <options>
    <option name="comment" value="this is field"/>
    <option name="unsigned" value="true"/>
    <option name="version" value="3"/>
  </options>
</field>
itemRecord:
  type: entity
  fields:
    item:
      type: string
      length: 255
      nullable: false
      column: itemRecord_name
      columnDefinition: itemRecord_name
      precision: 1
      scale: 1
      version: false
      generator: 
      sequence-generator: 
      options:
        comment: this is field
        unsigned: true
        version: 3
Doctrine 2 entity and selected field in Skipper ER diagram.

Generated by Skipper

You can go and see how to do this in few clicks.

Index

Indexes

Non-unique index:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 * @ORM\Table(
 *     indexes={@ORM\Index(name="ix_name_last", columns={"lastName"})}
 * )
 */
class author
<entity name="author">
  <field name="firstName"/>
  <field name="lastName"/>
  <field name="birthDate"/>
  <indexes>
    <index name="ix_name_last" columns="lastName"/>
  </indexes>
</entity>
author:
  type: entity
  fields:
    firstName:
    lastName:
    birthDate:
  indexes:
    ix_name_last:
      columns: [lastName]
Doctrine 2 entity in Skipper visual model

Generated by Skipper

Unique index definition:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 * @ORM\Table(
 *     uniqueConstraints={@ORM\UniqueConstraint(name="ix_first_name_last_name_date", columns={"firstName","lastName","birthDate"})}
 * )
 */
class author
<entity name="author">
  <field name="firstName"/>
  <field name="lastName"/>
  <field name="birthDate"/>
  <unique-constraints>
    <unique-constraint 
		 name="ix_first_name_last_name_date" 
		 columns="firstName,lastName,birthDate"/>
  </unique-constraints>
</entity>
author:
  type: entity
  fields:
    firstName:
    lastName:
    birthDate:
  uniqueConstraints:
    ix_first_name_last_name_date:
      columns: [firstName, lastName, birthDate]
Doctrine 2 entity in Skipper visual model

Generated by Skipper

You can go and see how to do this in few clicks.

Association

One to One

One to one owner side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\OneToOne(
     *     targetEntity="ean",
     *     inversedBy="itemRecord"
     * )
     * @ORM\JoinColumn(name="eanId", referencedColumnName="id", unique=true)
     */
    private $ean;
}
<entity name="itemRecord">
	<one-to-one field="ean" target-entity="ean" inversed-by="itemRecord">
		<join-columns>
			<join-column 
			 name="eanId" 
			 referenced-column-name="id" 
			 unique="true"/>
		</join-columns>
	</one-to-one>
</entity>
itemRecord:
  type: entity
  fields:
  oneToOne:
    ean:
      targetEntity: ean
      inversedBy: itemRecord
      joinColumns:
        eanId:
          referencedColumnName: id
Doctrine 2 one to one association in Skipper ER diagram.

Generated by Skipper

One to one inverse side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class ean
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\OneToOne(
     *     targetEntity="itemRecord",
     *     mappedBy="ean"
     * )
     */
    private $itemRecord;
}
<entity name="ean">
  <id name="id"/>
    <one-to-one 
	 field="itemRecord" 
	 target-entity="itemRecord" 
	 mapped-by="ean">
  </one-to-one>
</entity>
ean:
  type: entity
  fields:
    id:
  oneToOne:
    itemRecord:
      targetEntity: itemRecord
      mappedBy: ean
Doctrine 2 one to one association imported from schema definition files.

Generated by Skipper

Many to One

Many to one owner side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\ManyToOne(
     *     targetEntity="publisher",
     *     inversedBy="itemRecord"
     * )
     * @ORM\JoinColumn(
     *     name="publisherId",
     *     referencedColumnName="publisher_id",
     *     nullable=false
     * )
     */
    private $publisher;
}
<entity name="itemRecord">
  <many-to-one 
   field="publisher" 
   target-entity="publisher" 
   inversed-by="itemRecord" 
   fetch="EXTRA_LAZY" 
   orphan-removal="true">
    <join-columns>
	  <join-column 
	   name="publisher_id" 
	   referenced-column-name="id" 
	   nullable="false" 
	   column-definition="itemRecord_publisherId" 
	   on-delete="CASCADE" 
	   on-update="RESTRICT"/>
    </join-columns>
    <cascade>
	  <cascade-all/>
	  <cascade-merge/>
	  <cascade-persist/>
	  <cascade-refresh/>
	  <cascade-remove/>
    </cascade>
  </many-to-one>
</entity>
itemRecord:
  type: entity
  manyToOne:
    publisher:
      targetEntity: publisher
      inversedBy: itemRecord
      joinColumns:
        publisherId:
          referencedColumnName: publisher_id
Doctrine 2 many to one association in Skipper ER diagram.

Generated by Skipper

Many to one inverse side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class publisher
{
    /**
     * @ORM\Id
     */
    private $id;

    /**
     * @ORM\OneToMany(
     *     targetEntity="itemRecord",
     *     mappedBy="publisher"
     * )
     */
    private $itemRecord;
}
<entity name="publisher">
  <id name="id"/>
  <one-to-many 
   field="itemRecord" 
   target-entity="itemRecord" 
   mapped-by="publisher">
  </one-to-many>
</entity>
publisher:
  type: entity
  fields:
    id:
      id: true
  oneToMany:
    itemRecord:
      targetEntity: itemRecord
      mappedBy: publisher
Doctrine 2 many to one association imported from schema definition files.

Generated by Skipper

Association with all options enabled

Many to one owner side with all properties:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\ManyToOne(
     *     targetEntity="publisher",
     *     inversedBy="itemRecord",
     *     fetch="EXTRA_LAZY",
     *     orphanRemoval=true,
     *     cascade={"all","merge","persist","refresh","remove"}
     * )
     * @ORM\JoinColumn(
     *     name="publisherId",
     *     referencedColumnName="publisher_id",
     *     nullable=false,
     *     columnDefinition="itemRecord_publisherId",
     *     onDelete="CASCADE",
     *     onUpdate="RESTRICT"
     * )
     */
    private $publisher;
}
<entity name="itemRecord">
  <many-to-one 
   field="publisher" 
   target-entity="publisher" 
   inversed-by="itemRecord" 
   fetch="EXTRA_LAZY" 
   orphan-removal="true">
    <join-columns>
	  <join-column 
	   name="publisherId" 
	   referenced-column-name="publisher_id" 
	   nullable="false" 
	   column-definition="itemRecord_publisherId" 
	   on-delete="CASCADE" 
	   on-update="RESTRICT"/>
    </join-columns>
    <cascade>
	  <cascade-all/>
	  <cascade-merge/>
	  <cascade-persist/>
	  <cascade-refresh/>
	  <cascade-remove/>
    </cascade>
</many-to-one>
itemRecord:
  type: entity
  fields:
  manyToOne:
    ean:
      targetEntity: ean
      inversedBy: itemRecord
      fetch: LAZY
      orphanRemoval: true
      cascade: ["all", "merge", "persist", "refresh", "remove"]
      joinColumns:
        eanId:
          referencedColumnName: id
          unique: true
          onDelete: CASCADE
          onUpdate: RESTRICT
Doctrine 2 many to one association in Skipper ER diagram.

Generated by Skipper

Many to one inverse side with all properties:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class publisher
{
    /**
     * @ORM\Id
     * @ORM\Column(
     *     type="integer"
     * )
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     */
    private $id;

    /**
     * @ORM\OneToMany(
     *     targetEntity="itemRecord",
     *     mappedBy="publisher",
     *     fetch="EAGER",
     *     indexBy="id",
     *     cascade={"all","merge","persist","refresh","remove"}
     * )
     * @ORM\OrderBy({"id"="ASC"})
     */
    private $itemRecord;
}
<entity name="publisher">
  <id name="id"/>
  <one-to-many 
   field="itemRecord" 
   target-entity="itemRecord" 
   mapped-by="publisher" 
   fetch="EAGER" 
   index-by="id">
    <cascade>
	  <cascade-all/>
	  <cascade-merge/>
	  <cascade-persist/>
	  <cascade-refresh/>
	  <cascade-remove/>
    </cascade>
    <order-by>
	  <order-by-field name="id" direction="ASC"/>
    </order-by>
  </one-to-many>
</entity>
publisher:
  type: entity
  fields:
    id:
      id: true
  oneToMany:
    itemRecord:
      targetEntity: itemRecord
      mappedBy: publisher
      fetch: EAGER
      indexBy: id
      cascade: ["all", "merge", "persist", "refresh", "remove"]
      orderBy:
        id: ASC

Doctrine 2 many to one association imported from schema definition files.

Generated by Skipper

You can go and see how to do this in few clicks.

MN Association

Many to Many

Many to many owner side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class author
{
    /**
     * @ORM\Id
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="itemRecord", inversedBy="author")
     * @ORM\JoinTable(
     *     name="itemRecordHasAuthor",
     *     joinColumns={
     *         @ORM\JoinColumn(
     *             name="authorId",
     *             referencedColumnName="id",
     *             nullable=false
     *         )
     *     },
     *     inverseJoinColumns={@ORM\JoinColumn(name="bookId", referencedColumnName="itemRecord_id", nullable=false)}
     * )
     */
    private $itemRecord;
}
<entity name="itemRecord">
  <id name="id"/>
  <many-to-many 
   field="authors" 
   target-entity="author" 
   inversed-by="itemRecords">
    <join-table name="authorHasitemRecord">
      <join-columns>
        <join-column 
		name="item_record_id" 
		referenced-column-name="itemRecord_id" 
		nullable="false"/>
      </join-columns>
      <inverse-join-columns>
        <join-column 
		 name="author_id" 
		 referenced-column-name="id" 
		 nullable="false"/>
      </inverse-join-columns>
    </join-table>
  </many-to-many>
</entity>
itemRecord:
  type: entity
  manyToMany:
    authors:
      targetEntity: author
      inversedBy: itemRecords
      joinTable:
        name: authorHasitemRecord
        joinColumns:
          item_record_id:
            referencedColumnName: itemRecord_id
            nullable: false
        inverseJoinColumns:
          author_id:
            referencedColumnName: id
            nullable: false
Doctrine 2 many-to-many association in Skipper generated diagram.

Generated by Skipper

Many to many inverse side:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class itemRecord
{
    /**
     * @ORM\ManyToMany(targetEntity="author", mappedBy="itemRecord")
     */
    private $author;
}
<entity name="author">
  <id name="id"/>
  <many-to-many 
   field="itemRecords" 
   target-entity="itemRecord" 
   mapped-by="authors">
  </many-to-many>
</entity>
author:
  type: entity
  fields:
    id:
  manyToMany:
    itemRecords:
      targetEntity: itemRecord
      mappedBy: authors
Doctrine 2 many-to-many displayed in Skipper visual model.

Generated by Skipper

Many to many with all options enabled:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class author
{
    /**
     * @ORM\Id
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="itemRecord", inversedBy="author", cascade={"all","refresh"})
     * @ORM\JoinTable(
     *     name="itemRecordHasAuthor",
     *     joinColumns={
     *         @ORM\JoinColumn(
     *             name="authorId",
     *             referencedColumnName="id",
     *             nullable=false,
     *             fetch="EAGER",
     *             onDelete="CASCADE",
     *             onUpdate="RESTRICT"
     *         )
     *     },
     *     inverseJoinColumns={@ORM\JoinColumn(name="bookId", referencedColumnName="itemRecord_id", nullable=false)}
     * )
     * @ORM\OrderBy({"id"="ASC"})
     */
    private $itemRecord;
}
<entity name="author">
  <id name="id" type="integer">
    <generator strategy="AUTO"/>
  </id>
  <field name="firstName" type="string" nullable="false"/>
  <field name="lastName" type="string" nullable="false"/>
  <field name="birthDate" type="string" nullable="true"/>
  <field name="awards" type="string" nullable="true"/>
  <indexes>
    <index name="ix_name_last" columns="lastName"/>
  </indexes>
  <unique-constraints>
    <unique-constraint
     name="ix_first_name_last_name_date" 
     columns="firstName,lastName,birthDate"/>
  </unique-constraints>
  <many-to-many 
   field="itemRecords" 
   target-entity="itemRecord" 
   mapped-by="authors">
    <cascade>
      <cascade-all/>
      <cascade-merge/>
      <cascade-persist/>
      <cascade-refresh/>
      <cascade-remove/>
    </cascade>
    <order-by>
      <order-by-field name="lastName" direction="ASC"/>
    </order-by>
  </many-to-many>
</entity>
author:
  type: entity
  fields:
    id:
      id: true
  manyToMany:
    itemRecord:
      targetEntity: itemRecord
      inversedBy: author
      cascade: ["all", "refresh"]
      orderBy:
        id: ASC
      joinTable:
        name: itemRecordHasAuthor
        joinColumns:
          authorId:
            referencedColumnName: id
            nullable: false
            fetch: EAGER
            onDelete: CASCADE
            onUpdate: RESTRICT
        inverseJoinColumns:
          bookId:
            referencedColumnName: itemRecord_id
            nullable: false

MN Entity

Does not exist as a Doctrine2 object, it is handled internally.

You can go and see how to do this in few clicks.

Inheritance

Single table inheritance

Single table inheritance parent:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="item", type="string")
 * @ORM\DiscriminatorMap(
 *     {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
 * )
 */
class itemRecord
<entity name="itemRecord" inheritance-type="SINGLE_TABLE">
  <id name="id"/>
  <discriminator-column name="item" type="string"/>
	<discriminator-map>
		<discriminator-mapping class="itemRecord" value="itemRecord"/>
		<discriminator-mapping class="book" value="book"/>
		<discriminator-mapping class="magazine" value="magazine"/>
		<discriminator-mapping class="audioRecord" value="audioRecord"/>
	</discriminator-map>
</entity>
itemRecord:
  type: entity
  inheritanceType: SINGLE_TABLE
  repositoryClass: Doctrine\ORM\EntityRepository
  fields:
    id:
Doctrine 2 inheritance displayed in Skipper visual model.

Generated by Skipper

Single table inheritance child:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class book extends itemRecord
<entity name="book" extends="itemRecord">
  <field name="pages"/>
</entity>
book:
  type: entity
  extends: itemRecord
  fields:
Doctrine 2 inheritance generated in the Skipper ER diagram.

Generated by Skipper

Class table inheritance

Class table inheritance parent:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="item", type="string")
 * @ORM\DiscriminatorMap(
 *     {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
 * )
 */
class itemRecord
<entity name="itemRecord" inheritance-type="JOINED">
  <id name="id"/>
  <discriminator-column name="item" type="string"/>
  <discriminator-map>
    <discriminator-mapping class="itemRecord" value="itemRecord"/>
    <discriminator-mapping class="book" value="book"/>
    <discriminator-mapping class="magazine" value="magazine"/>
    <discriminator-mapping class="audioRecord" value="audioRecord"/>
  </discriminator-map>
</entity>
itemRecord:
  type: entity
  inheritanceType: JOINED
  repositoryClass: Doctrine\ORM\EntityRepository
  fields:
    id:
Doctrine 2 inheritance displayed in Skipper visual model.

Generated by Skipper

Class table inheritance child:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class book extends itemRecord
<entity name="book" extends="itemRecord">
  <field name="pages"/>
</entity>
book:
  type: entity
  extends: itemRecord
  fields:
Doctrine 2 inheritance generated in the Skipper ER diagram.

Generated by Skipper

Mapped superclass inheritance

Mapped superclass inheritance parent:

Annotations
XML
YML
Links
/**
 * 
 * @ORM\MappedSuperclass(repositoryClass="Doctrine\ORM\EntityRepository")
 */
class itemRecord
<mapped-superclass name="itemRecord">
  <id name="id"/>
</mapped-superclass>
itemRecord:
  type: mappedSuperclass
  repositoryClass: Doctrine\ORM\EntityRepository
  fields:
    id:
Doctrine 2 inheritance displayed in Skipper visual model.

Generated by Skipper

Mapped supperclass inheritance child:

Annotations
XML
YML
Links
/**
 * @ORM\Entity
 */
class book extends itemRecord
<entity name="book" extends="itemRecord">
  <field name="pages"/>
</entity>
book:
  type: entity
  extends: itemRecord
  fields:
Doctrine 2 inheritance generated in the Skipper ER diagram.

Generated by Skipper

You can go and see how to do this in few clicks.

Doctrine Extensions

Installing Doctrine extensions for Symfony2

Install Symfony-standard edition with composer:

  • git clone git://github.com/KnpLabs/symfony-with-composer.git example
  • cd example && rm -rf .git && php bin/vendors install
  • ensure your application loads and meets requirements

Add the gedmo/doctrine-extensions into composer.json

{
    "require": {
        "php":              ">=5.3.2",
        "symfony/symfony":  ">=2.0.9,<2.1.0-dev",
        "doctrine/orm":     ">=2.1.0,<2.2.0-dev",
        "twig/extensions":  "*",

        "symfony/assetic-bundle":         "*",
        "sensio/generator-bundle":        "2.0.*",
        "sensio/framework-extra-bundle":  "2.0.*",
        "sensio/distribution-bundle":     "2.0.*",
        "jms/security-extra-bundle":      "1.0.*",
        "gedmo/doctrine-extensions":      "dev-master"
    },

    "autoload": {
        "psr-0": {
            "Acme": "src/"
        }
    }
}

Update vendors: php composer.phar update gedmo/doctrine-extensions Configure your database connection parameters: app/config/parameters.ini

Mapping Doctrine2 for Symfony2

If you use translatable, tree or loggable extension you will need to map those abstract mappedsuperclasses. Add mapping info to your doctrine.orm configuration, edit app/config/config.yml:

doctrine:
    dbal:
# your dbal config here

    orm:
        auto_generate_proxy_classes: %kernel.debug%
        auto_mapping: true
# only these lines are added additionally 
        mappings:
            translatable:
                type: annotation
                alias: Gedmo
                prefix: Gedmo\Translatable\Entity
                # make sure vendor library location is correct
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"

After that, running php app/console doctrine:mapping:info you should see the output:

Found 3 entities mapped in entity manager default:
[OK]   Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
[OK]   Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation
[OK]   Gedmo\Translatable\Entity\Translation

Note: there is Gedmo\Translatable\Entity\Translation which is not a super class, in that case if you create doctrine schema, it will add ext_translations table, which might not be useful to you also. To skip mapping of these entities, you can map only superclasses

mappings:
    translatable:
        type: annotation
        alias: Gedmo
        prefix: Gedmo\Translatable\Entity
        # make sure vendor library location is correct
        dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity/MappedSuperclass"

The configuration above, adds a /MappedSuperclass into directory depth, after running php app/console doctrine:mapping:info you should only see now:

Found 2 entities mapped in entity manager default:
[OK]   Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation
[OK]   Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation

To map every extension use:

# only orm config branch of doctrine
orm:
    auto_generate_proxy_classes: %kernel.debug%
    auto_mapping: true
# only these lines are added additionally 
    mappings:
        translatable:
            type: annotation
            alias: Gedmo
            prefix: Gedmo\Translatable\Entity
            # make sure vendor library location is correct
            dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"
        loggable:
            type: annotation
            alias: Gedmo
            prefix: Gedmo\Loggable\Entity
            dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
        tree:
            type: annotation
            alias: Gedmo
            prefix: Gedmo\Tree\Entity
            dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"

Listener services for Doctrine2

Edit and create an yml service file in your app/config/doctrine_extensions.yml

# services to handle doctrine extensions
# import it in config.yml
services:
    # KernelRequest listener
    extension.listener:
        class: Acme\DemoBundle\Listener\DoctrineExtensionListener
        calls:
            - [ setContainer, [ @service_container ] ]
        tags:
            # translatable sets locale after router processing
            - { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 }
            # loggable hooks user username if one is in security context
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }


    # Doctrine Extension listeners to handle behaviors
    gedmo.listener.tree:
        class: Gedmo\Tree\TreeListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
            
    gedmo.listener.translatable:
        class: Gedmo\Translatable\TranslatableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
            - [ setDefaultLocale, [ %locale% ] ]
            - [ setTranslationFallback, [ false ] ]
    
    gedmo.listener.timestampable:
        class: Gedmo\Timestampable\TimestampableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
    
    gedmo.listener.sluggable:
        class: Gedmo\Sluggable\SluggableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
    
    gedmo.listener.sortable:
        class: Gedmo\Sortable\SortableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]
    
    gedmo.listener.loggable:
        class: Gedmo\Loggable\LoggableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]

Note: You will need to create Acme\DemoBundle\Listener\DoctrineExtensionListener if you use loggable or translatable behaviors. This listener will set the locale used from request and username to loggable.

<?php

// file: src/Acme/DemoBundle/Listener/DoctrineExtensionListener.php

namespace Acme\DemoBundle\Listener;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class DoctrineExtensionListener implements ContainerAwareInterface
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    public function onLateKernelRequest(GetResponseEvent $event)
    {
        $translatable = $this->container->get('gedmo.listener.translatable');
        $translatable->setTranslatableLocale($event->getRequest()->getLocale());
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
        if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
            $loggable = $this->container->get('gedmo.listener.loggable');
            $loggable->setUsername($securityContext->getToken()->getUsername());
        }
    }
}

Do not forget to import doctrine_extensions.yml in your app/config/config.yml etc.:

# file: app/config/config.yml
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: doctrine_extensions.yml }

# ... configuration follows

Installing Doctrine extensions for Zend Framework 2

Add DoctrineModule, DoctrineORMModule and DoctrineExtensions to composer.json file:

{
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.1.*",
        "doctrine/doctrine-module": "0.*",
        "doctrine/doctrine-orm-module": "0.*",
        "gedmo/doctrine-extensions": "2.3.*",
    }
}

Then run composer.phar update.

Configuring Doctrine extensions for Zend Framework 2

Declaring appropriate subscribers in Event Manager settings. With entity mapping options module configuration file should look like this:

return array(
    'doctrine' => array(
        'eventmanager' => array(
            'orm_default' => array(
                'subscribers' => array(
                
                    // pick any listeners you need
                    'Gedmo\Tree\TreeListener',
                    'Gedmo\Timestampable\TimestampableListener',
                    'Gedmo\Sluggable\SluggableListener',
                    'Gedmo\Loggable\LoggableListener',
                    'Gedmo\Sortable\SortableListener'
                ),
            ),
        ),
        'driver' => array(
            'my_driver' => array(
                'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(__DIR__ . '/../src/MyModule/Entity')
            ),
            'orm_default' => array(
                'drivers' => array(
                    'MyModule\Entity' => 'my_driver'
                ),
            ),
        ),
    ),
);

Behaviors

Tree

Tree nested behavior will implement the standard Nested-Set behavior on your Entity.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @Gedmo\Tree(type="nested")
 * @ORM\Table(name="categories")
 * use repository for handy tree functions
 * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
 */
class Category
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue
     */
    private $id;

    /**
     * @ORM\Column(name="title", type="string", length=64)
     */
    private $title;

    /**
     * @Gedmo\TreeLeft
     * @ORM\Column(name="lft", type="integer")
     */
    private $lft;

    /**
     * @Gedmo\TreeLevel
     * @ORM\Column(name="lvl", type="integer")
     */
    private $lvl;

    /**
     * @Gedmo\TreeRight
     * @ORM\Column(name="rgt", type="integer")
     */
    private $rgt;

    /**
     * @Gedmo\TreeRoot
     * @ORM\Column(name="root", type="integer", nullable=true)
     */
    private $root;

    /**
     * @Gedmo\TreeParent
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
     */
    private $parent;

    /**
     * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
     * @ORM\OrderBy({"lft" = "ASC"})
     */
    private $children;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setParent(Category $parent = null)
    {
        $this->parent = $parent;
    }

    public function getParent()
    {
        return $this->parent;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Mapping\Fixture\Xml\NestedTree" 
   table="nested_trees" 
   repository-class="Gedmo\Tree\Entity\Repository\NestedTreeRepository">

    <indexes>
      <index name="name_idx" columns="name"/>
    </indexes>

    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="name" type="string" length="128"/>
    <field name="left" column="lft" type="integer">
      <gedmo:tree-left/>
    </field>
    <field name="right" column="rgt" type="integer">
      <gedmo:tree-right/>
    </field>
    <field name="root" type="integer" nullable="true">
      <gedmo:tree-root/>
    </field>
    <field name="level" column="lvl" type="integer">
      <gedmo:tree-level/>
    </field>

    <many-to-one 
     field="parent" 
     target-entity="NestedTree" 
     inversed-by="children">
      <join-column 
       name="parent_id" 
       referenced-column-name="id" 
       on-delete="CASCADE"/>
      <gedmo:tree-parent/>
    </many-to-one>

    <one-to-many field="children" target-entity="NestedTree" mapped-by="parent">
      <order-by>
        <order-by-field name="left" direction="ASC" />
      </order-by>
    </one-to-many>

    <gedmo:tree type="nested"/>

  </entity>

</doctrine-mapping>
---
Entity\Category:
  type: entity
  repositoryClass: Gedmo\Tree\Entity\Repository\NestedTreeRepository
  table: categories
  gedmo:
    tree:
      type: nested
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
    lft:
      type: integer
      gedmo:
        - treeLeft
    rgt:
      type: integer
      gedmo:
        - treeRight
    root:
      type: integer
      nullable: true
      gedmo:
        - treeRoot
    lvl:
      type: integer
      gedmo:
        - treeLevel
  manyToOne:
    parent:
      targetEntity: Entity\Category
      inversedBy: children
      joinColumn:
        name: parent_id
        referencedColumnName: id
        onDelete: CASCADE
      gedmo:
        - treeParent
  oneToMany:
    children:
      targetEntity: Entity\Category
      mappedBy: parent
      orderBy:
        lft: ASC

For more details check the documentation.

Translatable

Translatable behavior offers a very handy solution for translating specific record fields in different languages.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Translatable;

/**
 * @ORM\Table(name="articles")
 * @ORM\Entity
 */
class Article implements Translatable
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

    /**
     * @Gedmo\Translatable
     * @ORM\Column(name="title", type="string", length=128)
     */
    private $title;

    /**
     * @Gedmo\Translatable
     * @ORM\Column(name="content", type="text")
     */
    private $content;

    /**
     * @Gedmo\Locale
     * Used locale to override Translation listener`s locale
     * this is not a mapped field of entity metadata, just a simple property
     */
    private $locale;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }

    public function getContent()
    {
        return $this->content;
    }

    public function setTranslatableLocale($locale)
    {
        $this->locale = $locale;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity 
   name="Mapping\Fixture\Xml\Translatable" 
   table="translatables">

    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="title" type="string" length="128">
      <gedmo:translatable/>
    </field>
    <field name="content" type="text">
      <gedmo:translatable/>
    </field>

    <gedmo:translation 
     entity="Gedmo\Translatable\Entity\Translation" 
     locale="locale"/>

  </entity>

</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  gedmo:
    translation:
      locale: localeField
# using specific personal translation class:
#     entity: Translatable\Fixture\CategoryTranslation
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
      gedmo:
        - translatable
    content:
      type: text
      gedmo:
        - translatable

For more details check the documentation.

Sluggable

Sluggable behavior will build the slug of predefined fields on a given field which should store the slug.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="articles")
 * @ORM\Entity
 */
class Article
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(length=64)
     */
    private $title;

    /**
     * @ORM\Column(length=16)
     */
    private $code;

    /**
     * @Gedmo\Slug(fields={"title", "code"})
     * @ORM\Column(length=128, unique=true)
     */
    private $slug;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setCode($code)
    {
        $this->code = $code;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getSlug()
    {
        return $this->slug;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
  <entity name="Entity\Article" table="sluggables">
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="title" type="string" length="128"/>
    <field name="code" type="string" length="16"/>
    <field name="ean" type="string" length="13"/>
    <field name="slug" type="string" length="156" unique="true">
      <gedmo:slug unique="true" style="camel" updatable="false" separator="_" fields="title,code,ean" />
    </field>
  </entity>
</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
    code:
      type: string
      length: 16
    slug:
      type: string
      length: 128
      gedmo:
        slug:
          separator: _
          style: camel
          fields:
            - title
            - code
  indexes:
    search_idx:
      columns: slug

For more details check the documentation.

Timestampable

Timestampable behavior will automate the update of date fields on your Entities or Documents.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $title;

    /**
     * @ORM\Column(name="body", type="string")
     */
    private $body;

    /**
     * @var datetime $created
     *
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(type="datetime")
     */
    private $created;

    /**
     * @var datetime $updated
     *
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(type="datetime")
     */
    private $updated;

    /**
     * @var datetime $contentChanged
     *
     * @ORM\Column(name="content_changed", type="datetime", nullable=true)
     * @Gedmo\Timestampable(on="change", field={"title", "body"})
     */
    private $contentChanged;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function getCreated()
    {
        return $this->created;
    }

    public function getUpdated()
    {
        return $this->updated;
    }

    public function getContentChanged()
    {
        return $this->contentChanged;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

    <entity 
     name="Mapping\Fixture\Xml\Timestampable" 
     table="timestampables">
        <id name="id" type="integer" column="id">
            <generator strategy="AUTO"/>
        </id>

        <field name="created" type="datetime">
            <gedmo:timestampable on="create"/>
        </field>
        <field name="updated" type="datetime">
            <gedmo:timestampable on="update"/>
        </field>
        <field name="published" type="datetime" nullable="true">
            <gedmo:timestampable 
             on="change" 
             field="status.title" 
             value="Published"/>
        </field>

        <many-to-one field="status" target-entity="Status">
            <join-column name="status_id" referenced-column-name="id"/>
        </many-to-one>
    </entity>

</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
    created:
      type: date
      gedmo:
        timestampable:
          on: create
    updated:
      type: datetime
      gedmo:
        timestampable:
          on: update

For more details check the documentation.

Blameable

Blameable behavior will automate the update of username or user reference fields on your Entities or Documents.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $title;

    /**
     * @ORM\Column(name="body", type="string")
     */
    private $body;

    /**
     * @var string $createdBy
     *
     * @Gedmo\Blameable(on="create")
     * @ORM\Column(type="string")
     */
    private $createdBy;

    /**
     * @var string $updatedBy
     *
     * @Gedmo\Blameable(on="update")
     * @ORM\Column(type="string")
     */
    private $updatedBy;

    /**
     * @var datetime $contentChangedBy
     *
     * @ORM\Column(name="content_changed_by", type="string", nullable=true)
     * @Gedmo\Timestampable(on="change", field={"title", "body"})
     */
    private $contentChangedBy;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function getCreatedBy()
    {
        return $this->createdBy;
    }

    public function getUpdatedBy()
    {
        return $this->updatedBy;
    }

    public function getContentChangedBy()
    {
        return $this->contentChangedBy;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Mapping\Fixture\Xml\Blameable" table="blameables">
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="createdBy" type="string">
      <gedmo:blameable on="create"/>
    </field>
    <field name="updatedBy" type="string">
      <gedmo:blameable on="update"/>
    </field>
    <field name="publishedBy" type="string" nullable="true">
      <gedmo:blameable on="change" field="status.title" value="Published"/>
    </field>

    <many-to-one field="status" target-entity="Status">
      <join-column name="status_id" referenced-column-name="id"/>
    </many-to-one>
  </entity>

</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
    createdBy:
      type: string
      gedmo:
        blameable:
          on: create
    updatedBy:
      type: string
      gedmo:
        blameable:
          on: update

For more details check the documentation.

Loggable

Loggable behavior tracks your record changes and is able to manage versions.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @Entity
 * @Gedmo\Loggable
 */
class Article
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @Gedmo\Versioned
     * @ORM\Column(name="title", type="string", length=8)
     */
    private $title;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Mapping\Fixture\Xml\Loggable" table="loggables">

    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="title" type="string" length="128">
      <gedmo:versioned/>
    </field>
    <many-to-one field="status" target-entity="Status">
      <join-column name="status_id" referenced-column-name="id"/>
      <gedmo:versioned/>
    </many-to-one>

    <gedmo:loggable log-entry-class="Gedmo\Loggable\Entity\LogEntry"/>

  </entity>
</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  gedmo:
    loggable:
# using specific personal LogEntryClass class:
      logEntryClass: My\LogEntry
# without specifying the LogEntryClass class:
#   loggable: true
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
      gedmo:
        - versioned
    content:
      type: text

For more details check the documentation.

Sortable

Sortable behavior will maintain a position field for ordering entities.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="items")
 * @ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
 */
class Item
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

    /**
     * @ORM\Column(name="name", type="string", length=64)
     */
    private $name;

    /**
     * @Gedmo\SortablePosition
     * @ORM\Column(name="position", type="integer")
     */
    private $position;

    /**
     * @Gedmo\SortableGroup
     * @ORM\Column(name="category", type="string", length=128)
     */
    private $category;

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setPosition($position)
    {
        $this->position = $position;
    }

    public function getPosition()
    {
        return $this->position;
    }

    public function setCategory($category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
  <entity name="Entity\Item" table="items">
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="name" type="string" length="128">
    </field>

    <field name="position" type="integer">
      <gedmo:sortable-position/>
    </field>
    <field name="category" type="string" length="128">
      <gedmo:sortable-group />
    </field>
  </entity>
</doctrine-mapping>
---
Entity\Item:
  type: entity
  table: items
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string
      length: 64
    position:
      type: integer
      gedmo:
        - sortablePosition
    category:
      type: string
      length: 128
      gedmo:
        - sortableGroup

For more details check the documentation.

Softdeleteable

SoftDeleteable behavior allows to “soft delete” objects, filtering them at SELECT time by marking them as with a timestamp, but not explicitly removing them from the database.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
 */
class Article
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @ORM\Column(name="title", type="string")
     */
    private $title;

    /**
     * @ORM\Column(name="deletedAt", type="datetime", nullable=true)
     */
    private $deletedAt;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function getDeletedAt()
    {
        return $this->deletedAt;
    }

    public function setDeletedAt($deletedAt)
    {
        $this->deletedAt = $deletedAt;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Mapping\Fixture\Xml\Timestampable" table="timestampables">
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="title" type="string" />

    <field name="deletedAt" type="datetime" nullable="true" />

    <gedmo:soft-deleteable field-name="deletedAt" time-aware="false" />
  </entity>

</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  gedmo:
    soft_deleteable:
      field_name: deletedAt
      time_aware: false
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
    deletedAt:
      type: date
      nullable: true

For more details check the documentation.

Uploadable

Uploadable behavior provides the tools to manage the persistence of files with Doctrine 2, including automatic handling of moving, renaming and removal of files and other features.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @Gedmo\Uploadable(path="/my/path", callback="myCallbackMethod", filenameGenerator="SHA1", allowOverwrite=true, appendNumber=true)
 */
class File
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @ORM\Column(name="path", type="string")
     * @Gedmo\UploadableFilePath
     */
    private $path;

    /**
     * @ORM\Column(name="name", type="string")
     * @Gedmo\UploadableFileName
     */
    private $name;

    /**
     * @ORM\Column(name="mime_type", type="string")
     * @Gedmo\UploadableFileMimeType
     */
    private $mimeType;

    /**
     * @ORM\Column(name="size", type="decimal")
     * @Gedmo\UploadableFileSize
     */
    private $size;


    public function myCallbackMethod(array $info)
    {
        // Do some stuff with the file..
    }

    // Other methods..
}
<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Entity\File" table="files">

    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="mimeType" column="mime" type="string">
      <gedmo:uploadable-file-mime-type />
    </field>

    <field name="size" column="size" type="decimal">
      <gedmo:uploadable-file-size />
    </field>

    <field name="name" column="name" type="string">
      <gedmo:uploadable-file-name />
    </field>

    <field name="path" column="path" type="string">
      <gedmo:uploadable-file-path />
    </field>

    <gedmo:uploadable
      allow-overwrite="true"
      append-number="true"
      path="/my/path"
      path-method="getPath"
      callback="callbackMethod"
      filename-generator="SHA1" />

  </entity>

</doctrine-mapping>
---
Entity\File:
  type: entity
  table: files
  gedmo:
    uploadable:
      allowOverwrite: true
      appendNumber: true
      path: '/my/path'
      pathMethod: getPath
      callback: callbackMethod
      filenameGenerator: SHA1
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    path:
      type: string
      gedmo:
        - uploadableFilePath
    path:
      type: string
      gedmo:
        - uploadableFileName
    mimeType:
      type: string
      gedmo:
        - uploadableFileMimeType
    size:
      type: decimal
      gedmo:
        - uploadableFileSize

For more details check the documentation.

ReferenceIntegrity

ReferenceIntegrity behavior will automate the reference integrity for referenced documents.

Annotations
YML
<?php
namespace Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ODM\Document(collection="types")
 */
class Type
{
    /**
     * @ODM\Id
     */
    private $id;

    /**
     * @ODM\String
     */
    private $title;

    /**
     * @ODM\ReferenceOne(targetDocument="Article", mappedBy="type")
     * @Gedmo\ReferenceIntegrity("nullify")
     * @var Article
     */
    protected $article;

    // ...
}
---
Document\Type:
  type: document
  collection: types
  fields:
      id:
          id:     true
      title:
          type:   string
      article:
          reference: true
          type: one
          mappedBy: type
          targetDocument: Document\Article
          gedmo:
              referenceIntegrity: nullify   # or restrict

For more details check the documentation.

IpTraceable

IpTraceable behavior will automate the update of IP trace on your Entities or Documents.

Annotations
XML
YML
<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $title;

    /**
     * @ORM\Column(name="body", type="string")
     */
    private $body;

    /**
     * @var string $createdFromIp
     *
     * @Gedmo\IpTraceable(on="create")
     * @ORM\Column(type="string", length=45, nullable=true)
     */
    private $createdFromIp;

    /**
     * @var string $updatedFromIp
     *
     * @Gedmo\IpTraceable(on="update")
     * @ORM\Column(type="string", length=45, nullable=true)
     */
    private $updatedFromIp;

    /**
     * @var datetime $contentChangedFromIp
     *
     * @ORM\Column(name="content_changed_by", type="string", nullable=true, length=45)
     * @Gedmo\IpTraceable(on="change", field={"title", "body"})
     */
    private $contentChangedFromIp;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function getCreatedFromIp()
    {
        return $this->createdFromIp;
    }

    public function getUpdatedFromIp()
    {
        return $this->updatedFromIp;
    }

    public function getContentChangedFromIp()
    {
        return $this->contentChangedFromIp;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping 
 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">

  <entity name="Mapping\Fixture\Xml\IpTraceable" table="ip-traceable">
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>

    <field name="createdFromIp" type="string", length="45", nullable="true">
      <gedmo:ip-traceable on="create"/>
    </field>
    <field name="updatedFromIp" type="string", length="45", nullable="true">
      <gedmo:ip-traceable on="update"/>
    </field>
    <field name="publishedFromIp" type="string" nullable="true", length="45">
      <gedmo:ip-traceable on="change" field="status.title" value="Published"/>
    </field>

    <many-to-one field="status" target-entity="Status">
      <join-column name="status_id" referenced-column-name="id"/>
    </many-to-one>
  </entity>

</doctrine-mapping>
---
Entity\Article:
  type: entity
  table: articles
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    title:
      type: string
      length: 64
    createdFromIp:
      type: string
      length: 45
      nullable: true
      gedmo:
        ipTraceable:
          on: create
    updatedFromIp:
      type: string
      length: 45
      nullable: true
      gedmo:
        ipTraceable:
          on: update

For more details check the documentation.

Skipper

Skipper application



Element definitions on this page were modelled and generated by Skipper, visual schema editor for ORM frameworks.

Skipper greatly simplifies work with Doctrine 2 and saves huge amount of time. Every example entity and relation used in this Cheatsheet can be achieved with just a few clicks with Skipper. Generated code is clean and elegant and complies with all coding standards.

To learn how Skipper works visit the product tour.

Export to Doctrine 2 definitions

Entity editor

Entity editor

Skipper allows to model and export the definition for every Doctrine 2 element and its properties. Further advantages of automated export are:

  • Editing and generating of definitions is fully repeatable.
  • Standardized definitions are immediately ready-to-use.
  • All typos and syntax errors are 100% eliminated.

Useful links:
Project export - more info about Skipper definitions export
Export to Doctrine 2 - how to export your changes to definition schema files

Import of a project

Export dialog

Export dialog

Any existing Doctrine 2 project can be simply and quickly imported to Skipper. This enables:

  • To visualize logic of any project.
  • To start to use application in any phase of the project.

Useful links:
Project import - general information about import feature
Doctrine 2 project import - how to import existing project to Skipper

Summary of Skipper benefits

Import dialog

Import dialog

  • Allows to create and maintain the project four times faster.
  • Replaces manual definitions writing and reduces errors.
  • Displays the model schema in a form of interactive enhanced ER diagram.
  • Emphasizes the creative part of a project and eliminates the stereotype.
  • Increases work comfort.
  • Provides quality project documentation.
  • Reduces requirements on knowledge and experience of programmers.
  • Simplifies the cooperation between team members.

Skipper download

Atlantic model

Atlantic model

You can try Skipper during its 14-day evaluation period. Trial version offers full application functionality without any limitation and no credit card is needed.

Download trial version from the tool websites at www.skipper18.com/download.

Download Skipper

See also

Do you know any other helpful or interesting sites that should be linked here?
Let us know: developers@ormcheatsheet.com


Found a typo? Something is wrong or missing in the Cheatsheet? Just fork and edit it!

ORM Cheat Sheet

Created by Inventic developed by community

If you want to leave feedback, contact us developers@ormcheatsheet.com


Fork me on GitHub Real Time Web Analytics