Create Entities

When working with OroPlatform, creating entities and mapping them to the database is no different from doing the same in a common Symfony application. Once 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 is 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, therefore, 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. To update the schema, use the doctrine:schema:update command. Use the --dump-sql option first to make sure that Doctrine makes the expected changes:

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

If the command displays unexpected information, double-check the configured mapping information and rerun the command.

When everything is displayed 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, clear the metadata cache manually and 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 using migrations in the Update Database Schema section. To run migrations and emulate complete migration process, use the oro:platform:update command.

Doctrine Entities

Define Entities

You can define entities the same way as in typical Symfony applications. For example, use the annotations provided by Doctrine (you can also use the YAML or XML configuration format):

 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 can create a class that represents a particular model from your domain and add the getter and setter methods to access and modify the data in your application. Next, add mapping information to tell Doctrine how the data is mapped to your database.

Update Database Schema

Once the models are ready, update the database to reflect the changes you have made. Use migrations as a mechanism to extend your model iteratively. Migrations allow you to version your database schema. Every time you modify your model, you 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 of handling different schema versions per bundle, which 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 relatively simple if you follow the basic conventions below:

  • 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. Therefore, it is a good practice to name them following the v1_0, v2_0 pattern.

When migration to a particular schema version is performed, all migration classes from the corresponding directory are evaluated. Then, 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 looks is illustrated below:

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

Queries executed using the QueryBag, are divided into two groups: use the addPreQuery() 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 is modified.

To load and apply migrations to the existing database schema, 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 is going to 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). You can also get more information about each query with the --show-queries option.