Doctrator
           Pablo Díez




Symfony Live 2011 - San Francisco
Pablo Díez
Creator of Mondongo
  ODM for MongoDB and PHP
  http://mondongo.es (in English :)
Creator of Mondator
  Class generator for PHP
Creator of Doctrator




http://twitter.com/pablodip
http://github.com/pablodip
What is Doctrator?
Doctrator = Doctrine2 + Mondator
Agile Development
Agile Development

  ActiveRecord
Agile Development

  ActiveRecord      optional
Agile Development
  ActiveRecord
    Behaviors
Agile Development
          ActiveRecord
             Behaviors

Doctrator saves you a lot of time! ;)
How does Doctrine2 work?
How does Doctrine2 work?

“Doctrine2 provides transparent persistence for PHP objects.”




                            http://www.doctrine-project.org/docs/orm/2.0/en/reference/introduction.html
That is, persist PHP objects without restrictions of a base class,
                      properties, methods.


                       namespace Model;

                       class User
                       {
                           public $id;
                           public $username;
                           public $email;
                       }
You only have to tell Doctrine2 (map) what you want to persist.
You only have to tell Doctrine2 (map) what you want to persist.

                 With Docblock Annotations

                    /**
                      * @Entity
                      */
                    class User
                    {
                         /**
                          * @Id
                          * @Column(type="integer")
                          */
                         public $id;

                        /**
                         * @Column(length=50)
                         */
                        public $username;

                        /**
                         * @Column(length=100)
                         */
                        public $email;
                    }
You only have to tell Doctrine2 (map) what you want to persist.

                            With YAML


            EntitiesUser:
                type: entity
                fields:
                    id:       { type: integer, id: true }
                    username: { type: string(50) }
                    email:    { type: string(50) }
You only have to tell Doctrine2 (map) what you want to persist.

                                          With XML


    <?xml version="1.0" encoding="UTF-8"?>
    <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                              http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

        <entity name="ModelUser" table="user">

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

            <field name="username" type="string" length="50" />
            <field name="email" type="string" length="100" />

        </entity>

    </doctrine-mapping>
You only have to tell Doctrine2 (map) what you want to persist.

                              With PHP


                     $metadata->mapField(array(
                         'id' => true,
                         'fieldName' => 'id',
                         'type' => 'integer'
                     ));

                     $metadata->mapField(array(
                         'fieldName' => 'username',
                         'type' => 'string'
                     ));

                     $metadata->mapField(array(
                        'fieldName' => 'email',
                        'type' => 'string'
                     ));
You only have to tell Doctrine2 (map) what you want to persist.


      Then you are able to persist those objects.
Then you are able to persist those objects.


         $user = new User();
         $user->username = 'pablodip';
         $user->password = 'pa$$word';
         $user->email = 'pablodip@gmail.com';

         $entityManager->persist($user);
         $entityManager->flush();
A Doctrine2 best practice is to use non public
         properties in the entities.

              class User
              {
                  protected   $id;
                  protected   $username;
                  protected   $password;
                  protected   $email;
              }
You have to create methods to access to the properties.

                     Setters & Getters

                public function setId($id)
                {
                    $this->id = $id;
                }

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

                public function setUsername($username)
                {
                    $this->username = $username;
                }

                public function getUsername()
                {
                    return $this->username;
                }

                public function setPassword($password)
                {
                    $this->password = $password;
What do you need to work with this simple table?


                         user
                   id           integer
                username        string
                password        string
                 email          string
namespace Model;

   class User
   {
   }



                Class

         user
   id             integer
username           string
password           string
 email             string
namespace Model;

protected   $id;            class User
protected   $username;      {
protected   $password;      }
protected   $email;


                                         Class
            Properties
                                  user
                            id             integer
                         username           string
                         password           string
                          email             string
namespace Model;

   protected       $id;                             class User
   protected       $username;                       {
   protected       $password;                       }
   protected       $email;


                                                                 Class
                  Properties
                                                           user
                                                    id             integer
                                               username             string
public function setId($id)
{

}
    $this->id = $id;
                                               password             string
public function getId()
{
    return $this->id;
                                                  email             string
}

public function setUsername($username)
{
    $this->username = $username;         Setters/Getters
}

public function getUsername()
{
    return $this->username;
}

public function setPassword($password)
{
    $this->password = $password;
namespace Model;

   protected       $id;                             class User
   protected       $username;                       {
   protected       $password;                       }
   protected       $email;


                                                                 Class
                                                                                     Mapping
                  Properties
                                                           user
                                                    id             integer
                                                                             /**
                                                                              * @Entity
                                               username             string    */
public function setId($id)
{                                                                                /**

}
    $this->id = $id;
                                               password             string        * @Id
                                                                                  * @Column(type="integer")
                                                                                  */
public function getId()
{
    return $this->id;
                                                  email             string       /**
                                                                                  * @Column(length=50)
}
                                                                                  */
public function setUsername($username)
{
    $this->username = $username;         Setters/Getters                         /**
                                                                                  * @Column(length=100)
}                                                                                 */

public function getUsername()
{
    return $this->username;
}

public function setPassword($password)
{
    $this->password = $password;
namespace Model;

                          /**
                            * @Entity
                            */
                          class User
                          {
                               /**
                                * @Id
                                * @Column(type="integer")
                                */
                               protected $id;

                              /**
                               * @Column(length="50")
                               */
                              protected $username;

                              /**
                               * @Column(length="40")
                               */
                              protected $password;

                              /**
         user                  * @Column(length="100")
                               */
                              protected $email;

   id           integer       public function setId($id)
                              {
                                  $this->id = $id;
                              }
username        string        public function getId()
                              {
                                  return $this->id;
password        string        }

                              public function setUsername($username)
                              {
 email          string        }
                                  $this->username = $username;


                              public function getUsername()
                              {
                                  return $this->username;
                              }

                              public function setPassword($password)
                              {
                                  $this->password = $password;
                              }

                              public function getPassword()
                              {
                                  return $this->password;
                              }

                              public function setEmail($email)
                              {
                                  $this->email = $email;
                              }

                              public function getEmail()
                              {
                                  return $this->email;
                              }
                          }
namespace Model;

                          /**
                            * @Entity
                            */
                          class User
                          {
                               /**
                                * @Id
                                * @Column(type="integer")
                                */
                               protected $id;

                              /**
                               * @Column(length="50")
                               */
                              protected $username;

                              /**
                               * @Column(length="40")
                               */
                              protected $password;

                              /**
         user                  * @Column(length="100")
                               */
                              protected $email;

   id           integer       public function setId($id)
                              {
                                  $this->id = $id;


                                                                       LORC
                              }
username        string        public function getId()
                              {
                                  return $this->id;
password        string        }

                              public function setUsername($username)
                              {
 email          string        }
                                  $this->username = $username;


                              public function getUsername()
                              {
                                  return $this->username;
                              }

                              public function setPassword($password)
                              {
                                  $this->password = $password;
                              }

                              public function getPassword()
                              {
                                  return $this->password;
                              }

                              public function setEmail($email)
                              {
                                  $this->email = $email;
                              }

                              public function getEmail()
                              {
                                  return $this->email;
                              }
                          }
LORC

Lines Of Repetitive Code
LORC
      Lines Of Repetitive Code


... and we still don’t have any features! :)
How many LORC do we need in a real database?
How many LORC do we need in a real database?

                    ...
Doctrator’s principle is to avoid writing LORC
What does Doctrator do?
What does Doctrator do?

Doctrator generates classes and maps them with Doctrine2
Doctrator generates classes and maps them with Doctrine2


    You only have to tell it the configuration of the classes.
In PHP


array(
    'ModelUser' => array(
        'columns' => array(
            'id'       => array('id' => 'auto', 'type' => 'integer'),
            'username' => array('type' => 'string', 'length' => 50),
            'password' => array('type' => 'string', 'length' => 40),
            'email'    => array('type' => 'string', 'length' => 100),
        ),
    ),
);
In YAML



ModelUser:
    columns:
        id:         {   id: auto, type: integer }
        username:   {   type: string, length: 50 }
        password:   {   type: string, length: 40 }
        email:      {   type: string, length: 100 }
ModelUser:
                                         columns:
                                             id:         {   id: auto, type: integer }
                                             username:   {   type: string, length: 50 }
                                             password:   {   type: string, length: 40 }
                                             email:      {   type: string, length: 100 }




This generates your object.                                              This maps your object.
     class User
     {

        protected $id;

        protected $username;
                                                                               /**
        protected $password;
                                                                                * @Entity
        protected $email;
                                                                                */
        public function setId($id)
        {
            $this->id = $id;
        }                                                                          /**
        public function getId()                                                     * @Id
        {
            return $this->id;                                                       * @Column(type="integer")
        }
                                                                                    */
        public function setUsername($username)
        {
            $this->username = $username;
        }                                                                          /**
        public function getUsername()                                               * @Column(length=50)
        {
            return $this->username;
                                                                                    */
        }

        public function setPassword($password)
        {
                                                                                   /**
        }
            $this->password = $password;                                            * @Column(length=100)
        public function getPassword()
                                                                                    */
        {
            return $this->password;
        }

         public function setEmail($email)
ModelUser:
    columns:
        id:         {   id: auto, type: integer }
        username:   {   type: string, length: 50 }
        password:   {   type: string, length: 40 }
        email:      {   type: string, length: 100 }




      You can start to work.


$user = new ModelUser();
$user->setUsername('pagafantas');
$user->setEmail('pagafantas@gmail.com');

$entityManager->persist($user);
$entityManager->flush();
How does Doctrator work?
How does Doctrator work?

Doctrator uses Mondator to generate the classes for you.
Mondator defines PHP classes.
namespace Model;

class User
{
    protected $username;

    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getUsername()
    {
        return $this->username;
    }
}
namespace Model;           Definition

class User
{                                      Properties
    protected $username;

    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getUsername()
    {
        return $this->username;
    }
}
                                              Methods
use MondongoMondatorDefinitionDefinition;
use MondongoMondatorDefinitionProperty;
use MondongoMondatorDefinitionMethod;
namespace Model;

class User
{
}
                              Full Class Name



$definition = new Definition('ModelUser');
protected $username;



                            Visibility   Name



$property = new Property('protected', 'username');
$definition->addProperty($property);
public function setUsername($username)
{
    $this->username = $username;
}


                      Visibility   Name         Arguments     Code


$method = new Method('public', 'setUsername', '$username', <<<EOF
        $this->username = $username;
EOF
);
$definition->addMethod($method);
You can define any PHP class.
Parent class
$definition->setParentClass('ModelBaseUser');


                    Interfaces
$definition->addInterface('ArrayAccess');


                     Abstract
$definition->setIsAbstract(true);
Default value
$property->setValue($defaultValue);



                      Static
$property->setIsStatic(true);
Abstract
$method->setIsAbstract(true);



                      Static
$method->setIsStatic(true);
Even with comments.
$definition->setDocComment(<<<EOF
/**
 * User Class.
 */
EOF
);

$method->setDocComment(<<<EOF
    /**
     * Set the username.
     *
     * @param string $username The username.
     */
EOF
);
Then you can export them with the Dumper.


      use MondongoMondatorDumper;

      $dumper = new Dumper($definition);
      $classCode = $dumper->dump();




      echo $classCode;
/**
  * User entity.
  */
class User
{
     protected $username;

    /**
      * Set the username.
      *
      * @param string $username The username.
      */
    public function setUsername($username)
    {
         $this->username = $username;
    }

    /**
      * Returns the username.
      *
      * @return string The username.
      */
    public function getUsername()
    {
         return $this->username;
    }
}
And save them in files.


file_put_contents($file, $codeClass);
Mondator Extensions
Mondator Extensions

Mondator uses extensions to generate similar classes
          in a powerful and flexible way.
The Mondator Extensions process the config classes
     to define what classes will be generated.
ModelUser:
               columns:
                   id:         {   id: auto, type: integer }
                   username:   {   type: string, length: 50 }
                   password:   {   type: string, length: 40 }
                   email:      {   type: string, length: 100 }




The Mondator Extensions process the config classes
     to define what classes will be generated.


          MondongoMondatorDefinitionDefinition
ModelUser:
            columns:
                id:         {   id: auto, type: integer }
                username:   {   type: string, length: 50 }
                password:   {   type: string, length: 40 }
                email:      {   type: string, length: 100 }



use MondongoMondatorExtension;

class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $this->class;
        $this->configClass;

           $this->definitions;
    }
}
ModelUser:
            columns:
                id:         {   id: auto, type: integer }
                username:   {   type: string, length: 50 }
                password:   {   type: string, length: 40 }
                email:      {   type: string, length: 100 }



use MondongoMondatorExtension;

class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $this->class;
        $this->configClass;

           $this->definitions;
    }
}
ModelUser:
            columns:
                id:         {   id: auto, type: integer }
                username:   {   type: string, length: 50 }
                password:   {   type: string, length: 40 }
                email:      {   type: string, length: 100 }



use MondongoMondatorExtension;

class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $this->class;
        $this->configClass;

           $this->definitions;
    }
}
ModelUser:
            columns:
                id:         {   id: auto, type: integer }
                username:   {   type: string, length: 50 }
                password:   {   type: string, length: 40 }
                email:      {   type: string, length: 100 }



use MondongoMondatorExtension;

class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $this->class;
        $this->configClass;

           $this->definitions;
    }
}                                                Definitions to generate
An extension can generate any definition.
use MondongoMondatorExtension;
use MondongoMondatorDefinitionDefinition;

class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $definition = new Definition($this->class);
        $this->definitions['entity'] = $definition;

        $definition = new Definition($this->class.'Repository');
        $this->definitions['repository'] = $definition;
    }
}
foreach ($this->configClass['columns'] as $name => $column) {

    $property = new Property('protected', $name);

    $this->definitions['entity']->addProperty($property);

}
foreach ($this->configClass['columns'] as $name => $column) {

      $setterName = 'set'.Inflector::camelize($name);
      $setter = new Method('public', $setterName, '$value', <<<EOF
          $this->$name = $value;
EOF
      );

      $this->definitions['entity']->addMethod($setter);
}
Different extensions can modify the same definition.
class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        $definition = new Definition($this->class);
        $this->definitions['entity'] = $defintion;

        $this->processColumns();
    }
}



class ArrayAccess extends Extension
{
    protected function doClassProcess()
    {
        $this->definitions['entity']->addInterface('ArrayAccess');

        $method = new Method('public', 'offsetGet', '$name', $code);
        $this->definitions['entity']->addMethod($method);

        // ...
    }
}
An extension can have options.
class Doctrator extends Extension
{
    protected function setUp()
    {
        $this->addOptions(array(
            'columns'      => true,
            'array_access' => true,
        ));
    }

    protected function doClassProcess()
    {
        if ($this->getOption('columns')) {
            $this->processColumns();
        }

        if ($this->getOption('array_access')) {
            $this->processArrayAccess();
        }
    }
}
You can process the extensions that you want.
$mondator = new MondongoMondatorMondator();
$mondator->setConfigClasses($configClasses);
$mondator->setExtensions(array(
    new DoctratorExtensionCore($options),
));
$mondator->process();
$mondator = new MondongoMondatorMondator();
$mondator->setConfigClasses($configClasses);
$mondator->setExtensions(array(
    new DoctratorExtensionCore($options),
    new DoctratorExtensionArrayAccess(),
));
$mondator->process();
$mondator = new MondongoMondatorMondator();
$mondator->setConfigClasses($configClasses);
$mondator->setExtensions(array(
    new DoctratorExtensionCore($options),
    new DoctratorExtensionArrayAccess(),
));
$mondator->process();




$article['title'] = 'Doctrator';
echo $article['title']; // Doctrator
$mondator = new MondongoMondatorMondator();
$mondator->setConfigClasses($configClasses);
$mondator->setExtensions(array(
    new DoctratorExtensionCore($options),
    //new DoctratorExtensionArrayAccess(),
));
$mondator->process();




$article['title'] = 'Doctrator';
echo $article['title']; // Doctrator
An extension can change the config class to extend
               another extension.
class Doctrator extends Extension
{
    protected function doClassProcess()
    {
        foreach ($this->configClass['columns'] as $name => $column) {
            // ...
        }
    }
}




class DateColumn extends Extension
{
    protected function doConfigClassProcess()
    {
        $this->configClass['columns']['date'] = array(
            'type' => 'date',
        )
    }
}
You can even use extensions in the config classes.
ModelArticle:
    columns:
        id:     { id: auto, type: integer }
        title: { type: string, length: 100 }
    behaviors:
        -
             class: DoctratorBehaviorTimestampable
             options: { }
And you can combine all these things to do what you
                      want.
Generated code is not necessarily magic code.
Doctrator’s generated code is really simple, non-
                 magical code.
Code even with PHPDoc.
You can use IDE Autocompletion.
Doctrator Extensions
Doctrator Extensions

                          Core

ArrayAccess   PropertyOverloading   ActiveRecord   Behaviors
Core


Generates and maps objects with Doctrine2.
Doctrator uses base classes to separate generated
             code from your code.
ModelUser
namespace Model;

class User extends ModelBaseUser
{
    // your code
}

namespace ModelBase;

class User
{
    // generated code
}
ModelArticle:
    table_name: articles
    columns:
        id:        { id: auto, type: integer }
        title:     { type: string, length: 100 }
        slug:      { type: string, length: 100 }
        content:   { type: string }
        is_active: { type: boolean, default: true }
        date:      { type: date }
    many_to_one:
        category: { class: ModelCategory, inversed: articles }
    indexes:
        slug: { columns: ['slug'], unique: true }
        date: { columns: ['is_active', 'date'] }
    events:
        preUpdate: ['updateDate']
Associations
ModelArticle:
    table_name: articles     one_to_one
                            one_to_many
    columns:
                            many_to_one
        id:      { id: auto,many_to_many
                             type: integer }
        title:   { type: string, length: 100 }
        slug:    { type: string, length: 100 }
                                 name
        content: { type: string class
                                  }
                               mapped
        is_active: { type: boolean, default: true }
        date:    { type: date }inversed
    many_to_one:
        category: { class: ModelCategory, inversed: articles }
    indexes:
        slug: { columns: ['slug'], unique: true }
        date: { columns: ['is_active', 'date'] }
    events:
        preUpdate: ['updateDate']
ModelArticle:               Events
    table_name: articles
    columns:                  prePersist
        id:      { id: auto, type: integer }
                              postPersist
        title:   { type: string, length: 100 }
                              preUpdate
        slug:                postUpdate
                 { type: string, length: 100 }
                             preRemove
        content: { type: string }
                             postRemove
        is_active: { type: boolean, default: true }
                               postLoad
        date:    { type: date }
    many_to_one:
        category: { class: ModelCategory, inversed: articles }
    indexes:
        slug: { columns: ['slug'], unique: true }
        date: { columns: ['is_active', 'date'] }
    events:
        preUpdate: ['updateDate']
$category = new ModelCategory();
$category->setName('Class Generator');
$entityManager->persist($category);

$article = new ModelArticle();
$article->setTitle('Doctrator');
$article->setDate(new DateTime('now'));
$article->setCategory($category);
$entityManager->persist($article);

$entityManager->flush();
Core

Useful methods.
Set & Get by string



$article->set('title', 'Doctrator');

echo $article->get('title'); // Doctrator
fromArray & toArray


$article->fromArray(array(
    'title' => 'Doctrator',
    'date' => new DateTime('now')
));

$array = $article->toArray();
ArrayAccess



Implements the ArrayAccess interface in the entities.
$article = new ModelArticle();
$article['title'] = 'Doctrator';
echo $article['title']; // Doctrator
PropertyOverloading


Allows you access to entity data like properties.
$article = new ModelArticle();
$article->title = 'Doctrator';
echo $article->title; // Doctrator
ActiveRecord


Implements the ActiveRecord pattern in your entities.
$article = new ModelArticle();
$article->setTitle('Doctrator');
$article->save();


$article->refresh();


$article->delete();
print_r($article);
print_r($article);



 ModelArticle Object
 (
     [id:protected] => 1
     [title:protected] => Doctrator
     [content:protected] => Rocks!
 )




Doctrator entities are clean even with ActiveRecord!
Doctrator uses a global object to save the EntityManager



  use DoctratorEntityManagerContainer;

  EntityManagerContainer::set($entityManager);
$em = ModelArticle::entityManager();
$articleRepository = ModelArticle::repository();
$queryBuilder = ModelArticle::queryBuilder();
$articles = $entityManager->getRepository('Model
Article')->findAll();
$article = $entityManager->getRepository('Model
Article')->find($id);




$articles = ModelArticle::repository()->findAll();
$article = ModelArticle::repository()->find($id);
Behaviors
Behaviors


Reuse features.
A behavior is simply a Mondator extension.
A behavior can

Have options
Change config classes:
 •   Columns
 •   Associations
 •   Indexes
 •   Events
 •   ...
Add new generated classes
Add properties and methods
 • Entities
 • Repositories
 • ...
Timestampable



Saves the created and updated date.
                created TRUE
        created_column created_at
               updated TRUE
       updated_column updated_at
ModelArticle:
    columns:
        id:    { id: auto, type: integer }
        title: { type: name, length: 100 }
    behaviors:
        - DoctratorBehaviorTimestampable
$article = new ModelArticle();
$article->setTitle('Doctrator');
$article->save();

echo $article->getCreatedAt(); // now
echo $article->getUpdatedAt(); // null

$article->setContent('Rocks!');
$article->save();

echo $article->getCreatedAt(); // before
echo $article->getUpdatedAt(); // now
Ipable



Saves the created and updated ip.
              created TRUE
      created_column created_from
             updated TRUE
      updated_column updated_from
Hashable
               Ipable



Saves a unique hash in each entity.
            column hash
ModelArticle:
    columns:
        id:    { id: auto, type: integer }
        title: { type: name, length: 100 }
    behaviors:
        - DoctratorBehaviorHashable
$article = new Article();
$article->setTitle('Doctrator');

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

echo $article->getHash();
// da39a3ee5e6b4b0d3255bfef95601890afd80709
Timestampable
           Sluggable



Saves a slug from a field.
    from_column *
    slug_column slug
         unique TRUE
         update FALSE
ModelArticle:
    columns:
        id:     { id: auto, type: integer }
        title: { type: name, length: 100 }
    behaviors:
        -
             class: DoctratorBehaviorSluggable
             options: { from_column: title }
$article = new ModelArticle();
$article->setTitle('Doctrator Rocks!');
$article->save();

echo $article->getSlug(); // doctrator-rocks
Sortable



Allows you to sort your entities.
             column position
        new_position bottom
$articles = array();
for ($i = 0; $i <= 10; $i++) {
    $articles[$i] = $a = new ModelArticle();
    $a->setTitle('Article '.$i);
    $a->save();
}

echo $articles[3]->getPosition(); // 3
echo $articles[6]->getPosition(); // 6
// some methods
$articles[1]->isFirst();
$articles[1]->isLast();
$articles[1]->getNext();
$articles[1]->getPrevious();
$articles[1]->swapWith($articles[2]);
$articles[1]->moveUp();
$articles[1]->moveDown();
 
$repository->getMinPosition();
$repository->getMaxPosition();
Taggable
                 Sortable



Allows you to save tags in the entities.
$article = new ModelArticle();
$article->setTitle('My Title');
$article->save();

// methods
$article->addTags('foobar, barfoo');
$article->removeTags('foobar');
$article->removeAllTags(); // saved and not saved
$article->getSavedTags();
$article->getTags(); // saved and not saved
$article->setTags(array('foo', 'bar'));
$article->saveTags();

$repository->getTags();
$repository->getTagsWithCount();
Translatable
                 Taggable
                 Sortable



Allows you to translate entity columns.
              columns *
ModelArticle:
    columns:
        id:       { id: auto, type: integer }
        title:    { type: string, length: 100 }
        content: { type: string }
        date:     { type: date }
    behaviors:
        -
             class: DoctratorBehaviorTranslatable
             options: { columns: ['title', 'content'] }
$article = new ModelArticle();
$article->setDate(new DateTime());

// en
$article->translation('en')->setTitle('My Title');
$article->translation('en')->setContent('My Content');

// es
$article->translation('es')->setTitle('Mi Título');
$article->translation('es')->setContent('Mi Contenido');

$article->save();
Doctrator in Symfony2
DoctratorBundle
doctrator.config:
    extensions:
        array_access:           false
        property_overloading:   false
        active_record:          true
        behaviors:              true

        validation:             true
doctrator.config:
    extensions:
        array_access:           false
        property_overloading:   false
        active_record:          true
        behaviors:              true

        validation:             true

        my_extension_id:        true
Config Classes


    app/config/doctrator/*.yml
*Bundle/Resources/doctrator/*.yml
Standard Namespace

ModelArticle:
    columns:
        id:      { id: auto, type: integer }
        title:   { type: string, length: 100 }
        content: { type: string }


ModelDoctratorUserBundleUser:
    columns:
        id:       { id: auto, type: integer }
        username: { type: string, length: 20 }
ModelArticle:
    validation:
        - MyArticleClassValidator: ~
    columns:
        id:      { id: auto, type: integer }
        title:   { type: string, length: 100 }
        content: { type: string, validation: [MaxLength: 2000] }




                        Validation integrated
php app/console doctrator:generate
Questions?

                 http://mondongo.es (English :)



You can contact me for Mondongo, Doctrator, consulting, development
                       pablodip@gmail.com

Doctrator Symfony Live 2011 San Francisco

  • 1.
    Doctrator Pablo Díez Symfony Live 2011 - San Francisco
  • 2.
    Pablo Díez Creator ofMondongo ODM for MongoDB and PHP http://mondongo.es (in English :) Creator of Mondator Class generator for PHP Creator of Doctrator http://twitter.com/pablodip http://github.com/pablodip
  • 3.
  • 4.
  • 5.
  • 6.
    Agile Development ActiveRecord
  • 7.
    Agile Development ActiveRecord optional
  • 8.
    Agile Development ActiveRecord Behaviors
  • 9.
    Agile Development ActiveRecord Behaviors Doctrator saves you a lot of time! ;)
  • 10.
  • 11.
    How does Doctrine2work? “Doctrine2 provides transparent persistence for PHP objects.” http://www.doctrine-project.org/docs/orm/2.0/en/reference/introduction.html
  • 12.
    That is, persistPHP objects without restrictions of a base class, properties, methods. namespace Model; class User { public $id; public $username; public $email; }
  • 13.
    You only haveto tell Doctrine2 (map) what you want to persist.
  • 14.
    You only haveto tell Doctrine2 (map) what you want to persist. With Docblock Annotations /** * @Entity */ class User { /** * @Id * @Column(type="integer") */ public $id; /** * @Column(length=50) */ public $username; /** * @Column(length=100) */ public $email; }
  • 15.
    You only haveto tell Doctrine2 (map) what you want to persist. With YAML EntitiesUser: type: entity fields: id: { type: integer, id: true } username: { type: string(50) } email: { type: string(50) }
  • 16.
    You only haveto tell Doctrine2 (map) what you want to persist. With XML <?xml version="1.0" encoding="UTF-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="ModelUser" table="user"> <id name="id" type="integer" column="id"> <generator strategy="AUTO"/> </id> <field name="username" type="string" length="50" /> <field name="email" type="string" length="100" /> </entity> </doctrine-mapping>
  • 17.
    You only haveto tell Doctrine2 (map) what you want to persist. With PHP $metadata->mapField(array( 'id' => true, 'fieldName' => 'id', 'type' => 'integer' )); $metadata->mapField(array( 'fieldName' => 'username', 'type' => 'string' )); $metadata->mapField(array( 'fieldName' => 'email', 'type' => 'string' ));
  • 18.
    You only haveto tell Doctrine2 (map) what you want to persist. Then you are able to persist those objects.
  • 19.
    Then you areable to persist those objects. $user = new User(); $user->username = 'pablodip'; $user->password = 'pa$$word'; $user->email = 'pablodip@gmail.com'; $entityManager->persist($user); $entityManager->flush();
  • 20.
    A Doctrine2 bestpractice is to use non public properties in the entities. class User { protected $id; protected $username; protected $password; protected $email; }
  • 21.
    You have tocreate methods to access to the properties. Setters & Getters public function setId($id) { $this->id = $id; } public function getId() { return $this->id; } public function setUsername($username) { $this->username = $username; } public function getUsername() { return $this->username; } public function setPassword($password) { $this->password = $password;
  • 22.
    What do youneed to work with this simple table? user id integer username string password string email string
  • 23.
    namespace Model; class User { } Class user id integer username string password string email string
  • 24.
    namespace Model; protected $id; class User protected $username; { protected $password; } protected $email; Class Properties user id integer username string password string email string
  • 25.
    namespace Model; protected $id; class User protected $username; { protected $password; } protected $email; Class Properties user id integer username string public function setId($id) { } $this->id = $id; password string public function getId() { return $this->id; email string } public function setUsername($username) { $this->username = $username; Setters/Getters } public function getUsername() { return $this->username; } public function setPassword($password) { $this->password = $password;
  • 26.
    namespace Model; protected $id; class User protected $username; { protected $password; } protected $email; Class Mapping Properties user id integer /** * @Entity username string */ public function setId($id) { /** } $this->id = $id; password string * @Id * @Column(type="integer") */ public function getId() { return $this->id; email string /** * @Column(length=50) } */ public function setUsername($username) { $this->username = $username; Setters/Getters /** * @Column(length=100) } */ public function getUsername() { return $this->username; } public function setPassword($password) { $this->password = $password;
  • 27.
    namespace Model; /** * @Entity */ class User { /** * @Id * @Column(type="integer") */ protected $id; /** * @Column(length="50") */ protected $username; /** * @Column(length="40") */ protected $password; /** user * @Column(length="100") */ protected $email; id integer public function setId($id) { $this->id = $id; } username string public function getId() { return $this->id; password string } public function setUsername($username) { email string } $this->username = $username; public function getUsername() { return $this->username; } public function setPassword($password) { $this->password = $password; } public function getPassword() { return $this->password; } public function setEmail($email) { $this->email = $email; } public function getEmail() { return $this->email; } }
  • 28.
    namespace Model; /** * @Entity */ class User { /** * @Id * @Column(type="integer") */ protected $id; /** * @Column(length="50") */ protected $username; /** * @Column(length="40") */ protected $password; /** user * @Column(length="100") */ protected $email; id integer public function setId($id) { $this->id = $id; LORC } username string public function getId() { return $this->id; password string } public function setUsername($username) { email string } $this->username = $username; public function getUsername() { return $this->username; } public function setPassword($password) { $this->password = $password; } public function getPassword() { return $this->password; } public function setEmail($email) { $this->email = $email; } public function getEmail() { return $this->email; } }
  • 29.
  • 30.
    LORC Lines Of Repetitive Code ... and we still don’t have any features! :)
  • 31.
    How many LORCdo we need in a real database?
  • 32.
    How many LORCdo we need in a real database? ...
  • 33.
    Doctrator’s principle isto avoid writing LORC
  • 34.
  • 35.
    What does Doctratordo? Doctrator generates classes and maps them with Doctrine2
  • 36.
    Doctrator generates classesand maps them with Doctrine2 You only have to tell it the configuration of the classes.
  • 37.
    In PHP array( 'ModelUser' => array( 'columns' => array( 'id' => array('id' => 'auto', 'type' => 'integer'), 'username' => array('type' => 'string', 'length' => 50), 'password' => array('type' => 'string', 'length' => 40), 'email' => array('type' => 'string', 'length' => 100), ), ), );
  • 38.
    In YAML ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 }
  • 39.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } This generates your object. This maps your object. class User { protected $id; protected $username; /** protected $password; * @Entity protected $email; */ public function setId($id) { $this->id = $id; } /** public function getId() * @Id { return $this->id; * @Column(type="integer") } */ public function setUsername($username) { $this->username = $username; } /** public function getUsername() * @Column(length=50) { return $this->username; */ } public function setPassword($password) { /** } $this->password = $password; * @Column(length=100) public function getPassword() */ { return $this->password; } public function setEmail($email)
  • 40.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } You can start to work. $user = new ModelUser(); $user->setUsername('pagafantas'); $user->setEmail('pagafantas@gmail.com'); $entityManager->persist($user); $entityManager->flush();
  • 41.
  • 42.
    How does Doctratorwork? Doctrator uses Mondator to generate the classes for you.
  • 43.
  • 44.
    namespace Model; class User { protected $username; public function setUsername($username) { $this->username = $username; } public function getUsername() { return $this->username; } }
  • 45.
    namespace Model; Definition class User { Properties protected $username; public function setUsername($username) { $this->username = $username; } public function getUsername() { return $this->username; } } Methods
  • 46.
  • 47.
    namespace Model; class User { } Full Class Name $definition = new Definition('ModelUser');
  • 48.
    protected $username; Visibility Name $property = new Property('protected', 'username'); $definition->addProperty($property);
  • 49.
    public function setUsername($username) { $this->username = $username; } Visibility Name Arguments Code $method = new Method('public', 'setUsername', '$username', <<<EOF $this->username = $username; EOF ); $definition->addMethod($method);
  • 50.
    You can defineany PHP class.
  • 51.
    Parent class $definition->setParentClass('ModelBaseUser'); Interfaces $definition->addInterface('ArrayAccess'); Abstract $definition->setIsAbstract(true);
  • 52.
    Default value $property->setValue($defaultValue); Static $property->setIsStatic(true);
  • 53.
    Abstract $method->setIsAbstract(true); Static $method->setIsStatic(true);
  • 54.
  • 55.
    $definition->setDocComment(<<<EOF /** * UserClass. */ EOF ); $method->setDocComment(<<<EOF /** * Set the username. * * @param string $username The username. */ EOF );
  • 56.
    Then you canexport them with the Dumper. use MondongoMondatorDumper; $dumper = new Dumper($definition); $classCode = $dumper->dump(); echo $classCode;
  • 57.
    /** *User entity. */ class User { protected $username; /** * Set the username. * * @param string $username The username. */ public function setUsername($username) { $this->username = $username; } /** * Returns the username. * * @return string The username. */ public function getUsername() { return $this->username; } }
  • 58.
    And save themin files. file_put_contents($file, $codeClass);
  • 59.
  • 60.
    Mondator Extensions Mondator usesextensions to generate similar classes in a powerful and flexible way.
  • 61.
    The Mondator Extensionsprocess the config classes to define what classes will be generated.
  • 62.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } The Mondator Extensions process the config classes to define what classes will be generated. MondongoMondatorDefinitionDefinition
  • 63.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } use MondongoMondatorExtension; class Doctrator extends Extension { protected function doClassProcess() { $this->class; $this->configClass; $this->definitions; } }
  • 64.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } use MondongoMondatorExtension; class Doctrator extends Extension { protected function doClassProcess() { $this->class; $this->configClass; $this->definitions; } }
  • 65.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } use MondongoMondatorExtension; class Doctrator extends Extension { protected function doClassProcess() { $this->class; $this->configClass; $this->definitions; } }
  • 66.
    ModelUser: columns: id: { id: auto, type: integer } username: { type: string, length: 50 } password: { type: string, length: 40 } email: { type: string, length: 100 } use MondongoMondatorExtension; class Doctrator extends Extension { protected function doClassProcess() { $this->class; $this->configClass; $this->definitions; } } Definitions to generate
  • 67.
    An extension cangenerate any definition.
  • 68.
    use MondongoMondatorExtension; use MondongoMondatorDefinitionDefinition; classDoctrator extends Extension { protected function doClassProcess() { $definition = new Definition($this->class); $this->definitions['entity'] = $definition; $definition = new Definition($this->class.'Repository'); $this->definitions['repository'] = $definition; } }
  • 69.
    foreach ($this->configClass['columns'] as$name => $column) { $property = new Property('protected', $name); $this->definitions['entity']->addProperty($property); }
  • 70.
    foreach ($this->configClass['columns'] as$name => $column) { $setterName = 'set'.Inflector::camelize($name); $setter = new Method('public', $setterName, '$value', <<<EOF $this->$name = $value; EOF ); $this->definitions['entity']->addMethod($setter); }
  • 71.
    Different extensions canmodify the same definition.
  • 72.
    class Doctrator extendsExtension { protected function doClassProcess() { $definition = new Definition($this->class); $this->definitions['entity'] = $defintion; $this->processColumns(); } } class ArrayAccess extends Extension { protected function doClassProcess() { $this->definitions['entity']->addInterface('ArrayAccess'); $method = new Method('public', 'offsetGet', '$name', $code); $this->definitions['entity']->addMethod($method); // ... } }
  • 73.
    An extension canhave options.
  • 74.
    class Doctrator extendsExtension { protected function setUp() { $this->addOptions(array( 'columns' => true, 'array_access' => true, )); } protected function doClassProcess() { if ($this->getOption('columns')) { $this->processColumns(); } if ($this->getOption('array_access')) { $this->processArrayAccess(); } } }
  • 75.
    You can processthe extensions that you want.
  • 76.
    $mondator = newMondongoMondatorMondator(); $mondator->setConfigClasses($configClasses); $mondator->setExtensions(array( new DoctratorExtensionCore($options), )); $mondator->process();
  • 77.
    $mondator = newMondongoMondatorMondator(); $mondator->setConfigClasses($configClasses); $mondator->setExtensions(array( new DoctratorExtensionCore($options), new DoctratorExtensionArrayAccess(), )); $mondator->process();
  • 78.
    $mondator = newMondongoMondatorMondator(); $mondator->setConfigClasses($configClasses); $mondator->setExtensions(array( new DoctratorExtensionCore($options), new DoctratorExtensionArrayAccess(), )); $mondator->process(); $article['title'] = 'Doctrator'; echo $article['title']; // Doctrator
  • 79.
    $mondator = newMondongoMondatorMondator(); $mondator->setConfigClasses($configClasses); $mondator->setExtensions(array( new DoctratorExtensionCore($options), //new DoctratorExtensionArrayAccess(), )); $mondator->process(); $article['title'] = 'Doctrator'; echo $article['title']; // Doctrator
  • 80.
    An extension canchange the config class to extend another extension.
  • 81.
    class Doctrator extendsExtension { protected function doClassProcess() { foreach ($this->configClass['columns'] as $name => $column) { // ... } } } class DateColumn extends Extension { protected function doConfigClassProcess() { $this->configClass['columns']['date'] = array( 'type' => 'date', ) } }
  • 82.
    You can evenuse extensions in the config classes.
  • 83.
    ModelArticle: columns: id: { id: auto, type: integer } title: { type: string, length: 100 } behaviors: - class: DoctratorBehaviorTimestampable options: { }
  • 84.
    And you cancombine all these things to do what you want.
  • 85.
    Generated code isnot necessarily magic code.
  • 86.
    Doctrator’s generated codeis really simple, non- magical code.
  • 87.
  • 88.
    You can useIDE Autocompletion.
  • 89.
  • 90.
    Doctrator Extensions Core ArrayAccess PropertyOverloading ActiveRecord Behaviors
  • 91.
    Core Generates and mapsobjects with Doctrine2.
  • 92.
    Doctrator uses baseclasses to separate generated code from your code.
  • 93.
    ModelUser namespace Model; class Userextends ModelBaseUser { // your code } namespace ModelBase; class User { // generated code }
  • 94.
    ModelArticle: table_name: articles columns: id: { id: auto, type: integer } title: { type: string, length: 100 } slug: { type: string, length: 100 } content: { type: string } is_active: { type: boolean, default: true } date: { type: date } many_to_one: category: { class: ModelCategory, inversed: articles } indexes: slug: { columns: ['slug'], unique: true } date: { columns: ['is_active', 'date'] } events: preUpdate: ['updateDate']
  • 95.
    Associations ModelArticle: table_name: articles one_to_one one_to_many columns: many_to_one id: { id: auto,many_to_many type: integer } title: { type: string, length: 100 } slug: { type: string, length: 100 } name content: { type: string class } mapped is_active: { type: boolean, default: true } date: { type: date }inversed many_to_one: category: { class: ModelCategory, inversed: articles } indexes: slug: { columns: ['slug'], unique: true } date: { columns: ['is_active', 'date'] } events: preUpdate: ['updateDate']
  • 96.
    ModelArticle: Events table_name: articles columns: prePersist id: { id: auto, type: integer } postPersist title: { type: string, length: 100 } preUpdate slug: postUpdate { type: string, length: 100 } preRemove content: { type: string } postRemove is_active: { type: boolean, default: true } postLoad date: { type: date } many_to_one: category: { class: ModelCategory, inversed: articles } indexes: slug: { columns: ['slug'], unique: true } date: { columns: ['is_active', 'date'] } events: preUpdate: ['updateDate']
  • 97.
    $category = newModelCategory(); $category->setName('Class Generator'); $entityManager->persist($category); $article = new ModelArticle(); $article->setTitle('Doctrator'); $article->setDate(new DateTime('now')); $article->setCategory($category); $entityManager->persist($article); $entityManager->flush();
  • 98.
  • 99.
    Set & Getby string $article->set('title', 'Doctrator'); echo $article->get('title'); // Doctrator
  • 100.
    fromArray & toArray $article->fromArray(array( 'title' => 'Doctrator', 'date' => new DateTime('now') )); $array = $article->toArray();
  • 101.
    ArrayAccess Implements the ArrayAccessinterface in the entities.
  • 102.
    $article = newModelArticle(); $article['title'] = 'Doctrator'; echo $article['title']; // Doctrator
  • 103.
    PropertyOverloading Allows you accessto entity data like properties.
  • 104.
    $article = newModelArticle(); $article->title = 'Doctrator'; echo $article->title; // Doctrator
  • 105.
  • 106.
    $article = newModelArticle(); $article->setTitle('Doctrator'); $article->save(); $article->refresh(); $article->delete();
  • 107.
  • 108.
    print_r($article); ModelArticle Object ( [id:protected] => 1 [title:protected] => Doctrator [content:protected] => Rocks! ) Doctrator entities are clean even with ActiveRecord!
  • 109.
    Doctrator uses aglobal object to save the EntityManager use DoctratorEntityManagerContainer; EntityManagerContainer::set($entityManager);
  • 110.
    $em = ModelArticle::entityManager(); $articleRepository= ModelArticle::repository(); $queryBuilder = ModelArticle::queryBuilder();
  • 111.
    $articles = $entityManager->getRepository('Model Article')->findAll(); $article= $entityManager->getRepository('Model Article')->find($id); $articles = ModelArticle::repository()->findAll(); $article = ModelArticle::repository()->find($id);
  • 112.
  • 113.
  • 114.
    A behavior issimply a Mondator extension.
  • 115.
    A behavior can Haveoptions Change config classes: • Columns • Associations • Indexes • Events • ... Add new generated classes Add properties and methods • Entities • Repositories • ...
  • 116.
    Timestampable Saves the createdand updated date. created TRUE created_column created_at updated TRUE updated_column updated_at
  • 117.
    ModelArticle: columns: id: { id: auto, type: integer } title: { type: name, length: 100 } behaviors: - DoctratorBehaviorTimestampable
  • 118.
    $article = newModelArticle(); $article->setTitle('Doctrator'); $article->save(); echo $article->getCreatedAt(); // now echo $article->getUpdatedAt(); // null $article->setContent('Rocks!'); $article->save(); echo $article->getCreatedAt(); // before echo $article->getUpdatedAt(); // now
  • 119.
    Ipable Saves the createdand updated ip. created TRUE created_column created_from updated TRUE updated_column updated_from
  • 120.
    Hashable Ipable Saves a unique hash in each entity. column hash
  • 121.
    ModelArticle: columns: id: { id: auto, type: integer } title: { type: name, length: 100 } behaviors: - DoctratorBehaviorHashable
  • 122.
    $article = newArticle(); $article->setTitle('Doctrator'); $entityManager->persist(); $entityManager->flush(); echo $article->getHash(); // da39a3ee5e6b4b0d3255bfef95601890afd80709
  • 123.
    Timestampable Sluggable Saves a slug from a field. from_column * slug_column slug unique TRUE update FALSE
  • 124.
    ModelArticle: columns: id: { id: auto, type: integer } title: { type: name, length: 100 } behaviors: - class: DoctratorBehaviorSluggable options: { from_column: title }
  • 125.
    $article = newModelArticle(); $article->setTitle('Doctrator Rocks!'); $article->save(); echo $article->getSlug(); // doctrator-rocks
  • 126.
    Sortable Allows you tosort your entities. column position new_position bottom
  • 127.
    $articles = array(); for($i = 0; $i <= 10; $i++) { $articles[$i] = $a = new ModelArticle(); $a->setTitle('Article '.$i); $a->save(); } echo $articles[3]->getPosition(); // 3 echo $articles[6]->getPosition(); // 6
  • 128.
  • 129.
    Taggable Sortable Allows you to save tags in the entities.
  • 130.
    $article = newModelArticle(); $article->setTitle('My Title'); $article->save(); // methods $article->addTags('foobar, barfoo'); $article->removeTags('foobar'); $article->removeAllTags(); // saved and not saved $article->getSavedTags(); $article->getTags(); // saved and not saved $article->setTags(array('foo', 'bar')); $article->saveTags(); $repository->getTags(); $repository->getTagsWithCount();
  • 131.
    Translatable Taggable Sortable Allows you to translate entity columns. columns *
  • 132.
    ModelArticle: columns: id: { id: auto, type: integer } title: { type: string, length: 100 } content: { type: string } date: { type: date } behaviors: - class: DoctratorBehaviorTranslatable options: { columns: ['title', 'content'] }
  • 133.
    $article = newModelArticle(); $article->setDate(new DateTime()); // en $article->translation('en')->setTitle('My Title'); $article->translation('en')->setContent('My Content'); // es $article->translation('es')->setTitle('Mi Título'); $article->translation('es')->setContent('Mi Contenido'); $article->save();
  • 134.
  • 135.
  • 136.
    doctrator.config: extensions: array_access: false property_overloading: false active_record: true behaviors: true validation: true
  • 137.
    doctrator.config: extensions: array_access: false property_overloading: false active_record: true behaviors: true validation: true my_extension_id: true
  • 138.
    Config Classes app/config/doctrator/*.yml *Bundle/Resources/doctrator/*.yml
  • 139.
    Standard Namespace ModelArticle: columns: id: { id: auto, type: integer } title: { type: string, length: 100 } content: { type: string } ModelDoctratorUserBundleUser: columns: id: { id: auto, type: integer } username: { type: string, length: 20 }
  • 140.
    ModelArticle: validation: - MyArticleClassValidator: ~ columns: id: { id: auto, type: integer } title: { type: string, length: 100 } content: { type: string, validation: [MaxLength: 2000] } Validation integrated
  • 141.
  • 142.
    Questions? http://mondongo.es (English :) You can contact me for Mondongo, Doctrator, consulting, development pablodip@gmail.com