La couche métier dans Symfony2: Les entités

Après avoir vu comment fonctionnaient les contrôleurs et vues dans Symfony2, passons au plus gros morceau: Les entités.




Génération de notre première Entity Symfony2


Les Entity dans Symfony2 sont des classes métiers qui décrivent chaque objet de notre application.
Dans notre application, nous retrouverons ainsi des Entity:
  • Desk
  • DeskComment
  • DeskPicture
  • DeskVote
  • Member


Commençons par créer notre entité Desk qui nous permettra de gérer les bureaux et faire l'interface avec Doctrine.

La console Symfony2 vous permettra de générer une entité facilement. Tapez la commande suivante:

php app/console generate:doctrine:entity


Génération de l entité Desk


Suivez les instructions et créés les champs comme je l'ai fais ci-dessus.
Si tout s'est bien passé, vous devriez avoir une classe Desk.php dans srcWmdWatchMyDeskBundleEntity.

Code php:
<?php

namespace Wmd\WatchMyDeskBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Wmd\WatchMyDeskBundle\Entity\Desk
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Wmd\WatchMyDeskBundle\Entity\DeskRepository")
 */
class Desk
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

    /**
     * @var text $summary
     *
     * @ORM\Column(name="summary", type="text")
     */
    private $summary;

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

    /**
     * @var decimal $note
     *
     * @ORM\Column(name="note", type="decimal")
     */
    private $note;

    /**
     * @var integer $voteCount
     *
     * @ORM\Column(name="voteCount", type="integer")
     */
    private $voteCount;

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

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

    /**
     * @var boolean $isEnabled
     *
     * @ORM\Column(name="isEnabled", type="boolean")
     */
    private $isEnabled;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * Get title
     *
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set summary
     *
     * @param text $summary
     */
    public function setSummary($summary)
    {
        $this->summary = $summary;
    }

    /**
     * Get summary
     *
     * @return text 
     */
    public function getSummary()
    {
        return $this->summary;
    }

    /**
     * Set description
     *
     * @param text $description
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

    /**
     * Get description
     *
     * @return text 
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set note
     *
     * @param decimal $note
     */
    public function setNote($note)
    {
        $this->note = $note;
    }

    /**
     * Get note
     *
     * @return decimal 
     */
    public function getNote()
    {
        return $this->note;
    }

    /**
     * Set voteCount
     *
     * @param integer $voteCount
     */
    public function setVoteCount($voteCount)
    {
        $this->voteCount = $voteCount;
    }

    /**
     * Get voteCount
     *
     * @return integer 
     */
    public function getVoteCount()
    {
        return $this->voteCount;
    }

    /**
     * Set createdAt
     *
     * @param datetime $createdAt
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
    }

    /**
     * Get createdAt
     *
     * @return datetime 
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt
     *
     * @param datetime $updatedAt
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;
    }

    /**
     * Get updatedAt
     *
     * @return datetime 
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * Set isEnabled
     *
     * @param boolean $isEnabled
     */
    public function setIsEnabled($isEnabled)
    {
        $this->isEnabled = $isEnabled;
    }

    /**
     * Get isEnabled
     *
     * @return boolean 
     */
    public function getIsEnabled()
    {
        return $this->isEnabled;
    }
}


La commande va automatiquement générer la classe avec les propriétés et leurs accesseurs/mutateurs (get et set).

Si vous observez les annotations sur la classe:
Code php:
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Wmd\WatchMyDeskBundle\Entity\DeskRepository")

Vous verrez que l'on défini qu'il s'agit d'une entité doctrine et qu'il faut donc la lier à une table de notre BDD.
Nous allons d'ailleurs changer le nom de la table pour ne pas qu'elle ai de majuscules. Ajoutez un paramètre name='desk' à l'annotation @Table():
Code php:
@ORM\Table(name="desk")


L'annotation suivante @ORM\Entity(repositoryClass, permet de définir la classe de Repository à lier avec l'entité. Une Repository class stocke toutes les requêtes Doctrine ou autre pour bien séparer les couches.

Toutes nos propriétés sont en private et décrites avec l'annotation ORM Columns:
Code php:
@ORM\Column(name="title", type="string", length=255)


C'est grâce à ces définitions que nous allons pouvoir générer notre base de données.

A noter aussi que par défaut, une propriété $id est automatiquement ajoutée:
Code php:
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;




Définir les valeurs par défaut dans le constructeur


Une fois l'entité générée, nous allons à présent créer le constructeur de la classe qui sera chargé d'initialiser toutes nos propriétés avec une valeur par défaut.

Ajoutez à la suite de vos propriétés le constructeur suivant:

Code php:
    public function __construct()
    {
        $this->voteCount = 0;
        $this->createdAt = new \DateTime('now');
        $this->isEnabled = false;
    }


Ainsi, lors de l'utilisation de formulaires, création d'objets... ces valeurs par défaut seront initialisées. Par contre, elles n'impactent pas les valeurs par défaut de votre base de données.




Définir les champs nullable et unique dans notre entité Symfony2


L'étape suivante, est de définir quels sont les champs qui pourront être nuls dans notre base de données.

Il suffit d'ajouter à l'annotation du champ la propriété nullable=true:

Code php:
 /**
     * @var decimal $note
     *
     * @ORM\Column(name="note", type="decimal", nullable=true)
     */
    private $note;

    /**
     * @var integer $voteCount
     *
     * @ORM\Column(name="voteCount", type="integer", nullable=true)
     */
    private $voteCount;

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

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


Il est aussi possible de définir quels champs vont être uniques en ajoutant la propriété unique=true.


Renommer les champs Doctrine2 en underscore case


De la même manière que pour le nom de la table liée à notre entité, nous allons renommer les champs pour respecter une convention "underscore case" et non camelCase.

Modifions la propriété name de l'annotation Column pour les champs avec du camelCase:

Code php:
    /**
     * @var integer $voteCount
     *
     * @ORM\Column(name="vote_count", type="integer", nullable=true)
     */
    private $voteCount;

    /**
     * @var datetime $createdAt
     *
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

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

    /**
     * @var boolean $isEnabled
     *
     * @ORM\Column(name="is_enabled", type="boolean")
     */
    private $isEnabled;


Voilà, notre entité est bien déclarée, on va pouvoir générer la table SQL qui lui est liée.


Générer la base de données depuis les entités Symfony2


Là encore, Symfony2 va nous simplifier la vie et nous permettre via une commande de générer la base de données de notre projet.

Assurez vous d'avoir bien créé et configuré votre base de données dans le fichier app/config/parameters.ini



Dans votre console, lancez la commande suivante:
php app/console doctrine:schema:update


vous devriez obtenir le message suivant:
Mise à jour de la BDD via la console Symfony2


Rien a été mis à jour pour le moment, on voit seulement qu'une requête va être effectuée pour la mise à jour de la BDD.
Comme nous sommes curieux, nous voulons vérifier quelle va être cette requête avant qu'elle ne soit exécutée dans notre BDD.
Ajoutez le paramètre --dump-sql à la commande:
php app/console doctrine:schema:update --dump-sql

Code sql:
CREATE TABLE desk (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, summary LONGTEXT NOT NULL, description LONGTEXT NOT NULL, note NUMERIC(10, 0) DEFAULT NULL, vote_count INT DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME DEFAULT NULL, is_enabled TINYINT(1) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB


La requête est correcte, on force la mise à jour de la BDD avec le paramètre --force:
php app/console doctrine:schema:update --force

Code:
Updating database schema...
Database schema updated successfully! "1" queries were executed


Tout s'est bien passé, on peut vérifier dans notre phpMyAdmin que la table a bien été générée:
La table desk a bien été créée dans notre BDD



Manipuler les entités Symfony2 et les sauvegarder en BDD


Les entités ne sont que de simples classes liées à la BDD via l'ORM Doctrine.
Pour créer une nouvelle entrée, il suffit de créer un objet de l'entité et de le "persister".

Créons à titre d'exemple une action qui va permettre de créer et sauvegarder un objet Desk.
Ouvrez le contrôleur DefaultController.php et ajoutez dans un premier temps l'utilisation du namespace de notre entité Desk.
Code php:
    /**
     * @Route("/test/", name="test")
     */
    public function testAction()
    {
        $desk = new Desk();
        $desk->setTitle("Bureau de fermier du web");
        $desk->setSummary("Voici pour vous en exclusivité mon bureau de Geek fermier !");
        $desk->setDescription("La description de mon bureau de geek avec ma config pc etc. Plus c'est Jacky, mieux c'est !");
        $desk->setIsEnabled(true);
        
        echo "Création du bureau: ".$desk->getTitle();
        
        exit;
    }


Si vous vous rendez à présent sur l'url /app_dev.php/test/ vous devriez avoir "Création du bureau: Bureau de fermier du web".
OK, maintenant, sauvegardons l'objet à l'aide de l'entity manager.

1) Initialisez l'entity manager
Code php:
$em = $this->getDoctrine()->getEntityManager();


2) Persistez l'objet dans l'entity manager
Code php:
$em->persist($desk);


Il est possible d'ajouter plusieurs objets à persister tout au long de l'exécution de notre action. Ils seront tous insérés de la manière la plus optimisée une fois qu'on l'aura décidé.



3) On flush l'entity manager pour que les données soient insérées ou mises à jour en BDD
Code php:
$em->flush();


Ajoutez ces trois lignes de code dans l'action test:
Code php:
    public function testAction()
    {
        $desk = new Desk();
        $desk->setTitle("Bureau de fermier du web");
        $desk->setSummary("Voici pour vous en exclusivité mon bureau de Geek fermier !");
        $desk->setDescription("La description de mon bureau de geek avec ma config pc etc. Plus c'est Jacky, mieux c'est !");
        $desk->setIsEnabled(true);
        
        echo "Création du bureau: ".$desk->getTitle();
        
        $em = $this->getDoctrine()->getEntityManager();
        $em->persist($desk);
        $em->flush();
        
        echo "Le bureau a été enregistré en BDD avec l'ID: ".$desk->getId();
        
        exit;
    }


Et l'on obtient bien:
Code:
Création du bureau: Bureau de fermier du web
Le bureau a été enregistré en BDD avec l'ID: 1


L'objet sauvegardé récupère automatiquement son ID, pratique.

Comment récupérer un objet depuis la BDD avec Symfony2 et Doctrine ?



Nous allons remodifier notre action de test pour voir comment récupérer notre objet depuis la BDD:
Code php:
    public function testAction()
    {
        
        $id = 1; // ID du bureau de test que l'on a enregistré précédemment
        
        $desk = $this->getDoctrine()->getRepository('WmdWatchMyDeskBundle:Desk')->find($id);
        echo "Le bureau récupéré porte l'ID: ".$desk->getId()." et le titre: ".$desk->getTitle();
        
        exit;
    }


Nous abordons à présent la notion de repository. Si vous vous souvenez bien, nous avons généré avec notre entité Desk son repository associé DeskRepository.php.
Ce dernier va nous permettre de stocker et séparer les appels en BDD, la création des requêtes dbal / dql ...

Toutes les méthodes présentes dans la classe DeskRepository seront accessibles de cette manière:
Code php:
$this->getDoctrine()->getRepository('WmdWatchMyDeskBundle:Desk')->nomDeLaMethode($paramsDeLaMethode);


Si vous regardez bien le repository, vous verrez qu'il étend une classe "EntityRepository". En allant jeter un coup d'oeil à cette dernière située dans vendor/doctrine/lib/Doctrine/ORM/EntityRepository.php, vous verrez qu'elle est dotée de plusieurs méthodes de base:
  • find($id): Pour récupérer une entité par sa clé primaire / id
  • findAll(): Pour récupérer toutes les entités sans distinction
  • findBy($criteria, $orderBy, $limit, $offset): Pour récupérer les entités suivant une liste de critères
  • findOneBy($criteria): Récupérer l'entité qui répond aux critères donnés


Je vous recommande d'être curieux et d'aller voir comment fonctionnent les classes parentes, interfaces ... de Symfony2. Tout le core est situé dans le répertoire vendor. Avec un bon IDE, vous devriez pouvoir ouvrir la classe correspondante à coup de CTRL + clic sur le nom de la classe. Vous en apprendrez plus que la doc ! Ne restez pas en mode développeur passif.



Testons de récupérer notre bureau précédemment créé en rafraichissant la page de notre action de test:
Code:
Le bureau récupéré porte l'ID: 1 et le titre: Bureau de fermier du web


Simple non ?
Pour récupérer de façon plus complexe des entités, nous créerons des méthodes spécifiques dans la classe repository ou dans un manager (nous aborderons cette notion plus tard dans le tuto).

Voilà pour ce 5ème chapitre, prochaine étape, les relations entre les entités dans Symfony2 et Doctrine2.





Rechercher sur la Ferme du web