Entities

Symfony2 and Doctrine provide a very powerful Model layer, but not a simple one. Coming from other frameworks, you might be expecting to write some code of the form:

[php]
class Page extends Record {
// Field definition
$fields = array(
"id" => "int",
"title" => "varchar(255)",
"content" => "text"
);

static function getPages() {
// query the database and return a collection of pages
// …
}
}
[/php]

The code above accomplishes two things: data mapping and querying,. In Symfony2, these two elements are extracted from the base object.

Data mapping

There are two ways of doing this in Symfony2. (Well, at least two. If there’s one thing I’m learning about Symfony2, it’s that there’s always more than one way to skin a cat.)

1. Doctrine annotations

You can tell Doctrine about your data model by creating a PHP object to represent it, and providing column descriptions and constraints in the form of annotations.

[php]
<!–?php<br /–>
// src/MyCompany/TestBundle/Entity/Page.php

use Doctrine\ORM\Mapping as ORM;

class Page
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\Column(type="string", length="200")
* @Assert\NotBlank()
*/
protected $title;
[/php]

When you run the console command php app/console doctrine:schema:create, Doctrine will create the table defined by these annotations.

2. ORM config file

Rather than starting with a PHP object, and processing it to create tables, you can instead start with a YML config file, and process that file to create both the database and the PHP representation of the entity.

The config file will look like this:

# src/MyCompany/TestBundle/Resources/config/doctrine/Page.orm.yml
MyCompany\TestBundle\Entity\Page:
    type: entity
    table: page
    id:
        id:
            type: integer
            generator:
                strategy: AUTO
    fields:
        title:
            type: string
            length: 255
            notblank: true

The location of these files is important; there are several places you can keep them. In order of specificity:

  • One file per Entity: Resources/config/doctrine/Page.orm.yml
  • All Entities in a combined file in the bundle: Resources/config/doctrine/mapping.orm.yml
  • All Entities for all bundles in a global file: /app/config/mapping.orm.yml.

To use the final method, you need to define a global mapping in your config.yml file as follows:

doctrine:
    orm:
        mappings:
            global:
                type:   yml
                dir:    %kernel.root_dir%/config
                prefix: MyCompany

Your prefix should be as precise as possible. If you’re only loading schemas for one bundle, your prefix should be MyCompany/MyBundle. If you need to load various schemas, make the prefix MyCompany as above.

Querying

Rather than putting queries in the same PHP files as your Entity definitions, Symfony2 offers the Repository pattern embraced by Doctrine. For more information about the role of the Repository, see its glossary entry.

[php]
<?php

namespace MyCompany\TestBundle\Repository;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->_em->createQuery(‘SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"’)
->getResult();
}
}
[/php]

To link the Repository to an Entity, define the repository class in the Entity’s mapping:

# src/MyCompany/TestBundle/Resources/config/doctrine/Page.orm.yml
MyCompany\TestBundle\Entity\Page:
    type: entity
    table: page
    repositoryClass: MyCompany\TestBundle\Repository\UserRepository
    #... as above

Designing your Model correctly

When you’re implementing your model, there’s always going to be a couple of methods which don’t really fit anywhere. Often this means that you can put them pretty much anywhere, as long as they’re accessible, since they’re usually not that critical. It might make a purist scratch their head a little bit, but it’s not going to be the end of the world.

In Symfony2, however, you’ll often find that the framework’s specifically designed to stop you from making bad decisions. For instance, there’s no way to access the database layer from an Entity; this is to enforce the separation of concerns between representation and persistence.

As a guideline:

Any method that just deals with the data in one given Entity (and its related objects, such as parents or associations) should go in the Entity file.

For methods which need to get into the database, for example to check for uniqueness or to count other records, you have a couple of options. You could define the query in a custom repository, then call it from the controller. If the method’s not particularly related to what the controller is doing, you might want to define an EventListener or a Symfony2 Service which you can then make available to whatever it is that’s doing the calling.

If you don’t have a good candidate for a Service which you can put the method into, you can always make another Manager class, for instance a PageManager, and inject the EntityManager into that service. This provides an additional level of abstraction and means you’re not tied to the ORM/ODM for this logic. See the KNPLabs’ UserBundle for an example.

Controllers are a good place to look if you’re trying to figure out where to *call* these methods from. It’s a good spot where you’ve got access to everything and you can organise what’s happening. But the methods should be defined elsewhere, so it’s easy to organise the content of controller actions into things that you can invoke on other objects.

Leave a Reply

Your email address will not be published. Required fields are marked *