Create Entities

When working with OroPlatform, creating entities and map them to the database is not different from doing the same in a common Symfony application. After you have created your bundle, create the entity classes you need in the bundle’s Entity namespace, add all the required properties, and add the required mapping annotations as usual.

A task is composed of a brief subject, a more verbose description, a due date, and a priority. Also, each task is identified by a unique identifier that will be automatically generated by the database:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// src/AppBundle/Entity/Task.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

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

    /**
     * @ORM\Column(type="string")
     *
     * @var string
     */
    private $subject;

    /**
     * @ORM\Column(type="text")
     *
     * @var string
     */
    private $description;

    /**
     * @ORM\Column(type="datetime", name="due_date")
     *
     * @var \DateTime
     */
    private $dueDate;

    /**
     * @ORM\ManyToOne(targetEntity="Priority")
     * @ORM\JoinColumn(name="task_priority_id", onDelete="SET NULL")
     *
     * @var Priority
     */
    private $priority;

    /**
     * Returns the id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Returns the subject.
     *
     * @return string
     */
    public function getSubject()
    {
        return $this->subject;
    }

    /**
     * Sets the subject.
     *
     * @param string $subject
     */
    public function setSubject($subject)
    {
        $this->subject = $subject;
    }

    /**
     * Returns the description.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Sets the description.
     *
     * @param string $description
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

    /**
     * Returns the due date.
     *
     * @return \DateTime
     */
    public function getDueDate()
    {
        return $this->dueDate;
    }

    /**
     * Sets the due date.
     *
     * @param \DateTime $dueDate
     */
    public function setDueDate(\DateTime $dueDate)
    {
        $this->dueDate = $dueDate;
    }

    /**
     * Returns the priority.
     *
     * @return Priority
     */
    public function getPriority()
    {
        return $this->priority;
    }

    /**
     * Sets the priority.
     *
     * @param Priority $priority
     */
    public function setPriority(Priority $priority)
    {
        $this->priority = $priority;
    }
}

Users should be able to create and change priorities through the user interface. Thus, they are modeled as separate entities:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// src/AppBundle/Entity/Priority.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

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

    /**
     * @ORM\Column(type="string", unique=true)
     *
     * @var string
     */
    private $label;

    /**
     * Returns the priority id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Returns the label.
     *
     * @return string
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * Changes the priority label.
     *
     * @param string $label
     */
    public function setLabel($label)
    {
        $this->label = $label;
    }
}

After you have modeled your entities, you need to update the database schema. This can be done by using the doctrine:schema:update command. Use the --dump-sql option to first make sure that Doctrine will actually make the expected changes:

$ php bin/console doctrine:schema:update --dump-sql

If the command displays something you did not expect, double-check the configured mapping information and rerun the command.

When everything displays as expected, update the database schema by passing the --force option:

$ php bin/console doctrine:schema:update --force

Tip

Doctrine caches mapping metadata. If the doctrine:schema:update command does not recognize your changes to the entity mapping, you can clear the metadata cache manually and try to update the schema again:

# clear the metadata cache
$ php bin/console doctrine:cache:clear-metadata

# check the schema change queries to be executed
$ php bin/console doctrine:schema:update --dump-sql

# apply the schema changes to the database
$ php bin/console doctrine:schema:update --force

Caution

Do not use the doctrine:schema:update command with your production database. Instead, create migrations to update the schema of your database. You can read more about how to use migrations here. To run migrations and emulate complete migration process, use the oro:platform:update command.

Doctrine Entities

Defining Entities

You can define entities in the same way that you are used to from common Symfony applications. For example, you can use the annotations provided by Doctrine (of course, you can use the YAML or XML configuration format as well):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// src/Acme/DemoBundle/Entity/Hotel.php
namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

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

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

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

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

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

You create a class that represents a particular model from your domain and add getter and setter methods to be able to access and modify the data in your application. Then, you add mapping information to tell Doctrine how the data will be mapped to your database.

Updating Database Schema

Once the models have been created, you need to update the database to reflect the changes you have done. So you need a mechanism to iteratively extend your model. For this purpose, you usually use migrations. Migrations allow you to version your database schema. Everytime you modify your model, you’ll create a new migration that reflects the changes for this particular schema version.

However, Doctrine’s migration mechanism only works well on the application level. It is not capable to handle different schema versions per bundle. This means that you cannot use them in a modular architecture. Luckily, you can use the features provided by the OroMigrationBundle to create separate migrations for each bundle.

Organizing migrations is pretty easy if you follow some basic conventions:

  • Place all migrations under the Migrations/Schema/ directory of your bundle.
  • In this directory, create one subdirectory per schema version.
  • Create as many migration classes as necessary inside a particular schema version directory (see the example below).

Note

The names of the schema version directories are compared to each other using PHP’s version_compare function. So it’s good practice to name them like v1_0, v2_0 and so on.

When a migration to a particular schema version is performed, all migration classes from the corresponding directory are evaluated and the contents of their up() method is executed. A class is treated as a migration class when it implements the Migration interface.

For example, the migration class for the Hotel entity will look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// src/Acme/DemoBundle/Migrations/Schema/v1_0/Hotel.php
namespace Acme\DemoBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;

class Hotel implements Migration
{
    public function up(Schema $schema, QueryBag $queries)
    {
        $table = $schema->createTable('acme_hotel');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        $table->addColumn('name', 'string', ['length' => 255]);
        $table->setPrimaryKey(['id']);
        $table->addIndex(['name'], 'hotel_name_idx', []);
    }
}

You can modify the database using the interface the Doctrine DBAL offers with its Schema class and you can also execute queries directly using the QueryBag if needed.

Queries that are executed using the QueryBag are divided into two groups: use the addPreQuery() method to add a query that is executed before the schema changes from the migration class are performed. Queries scheduled with the addPostQuery() method are executed after the schema has been modified.

To actually load and apply the migrations to the existing database schema, you have to execute the oro:migration:load command:

$ php bin/console oro:migration:load --force

This command checks for present migration versions that are currently not reflected in the existing database schema and executes all missing migrations sequentially in ascending order.

Tip

You can use the --dry-run option to see what would be executed and you can use the --bundles option to perform migrations only for a subset of all available bundles (use --exclude for a bundle blacklist instead). Also, you can get more information about each query with the --show-queries option.