Les scénarios du précédent chapitre décrivent les objets principaux de notre projet: les offres, les partenaires et les catégories. Voici le schéma des entités correspondant:
En plus des colonnes décrites dans les scénarios, nous avons également ajouté les colonnes created_at et updated_at. Nous allons configurer Symfony2 pour définir leur valeur automatiquement quand un objet est enregistré ou mis à jour.
Pour stocker les offres, les partenaires et les catégories dans la BDD, Symfony2 utilise Doctrine ORM. Pour définir les paramètres de connexion, vous devez éditer le fichier app/config/parameters.ini (pour ce tutoriel, nous allons utiliser MySQL):
;app/config/parameters.ini [parameters] database_driver = pdo_mysql database_host = localhost database_name = jobeet database_user = root database_password = password
Maintenant que Doctrine connaît votre BDD, vous pouvez l'utiliser pour créer la BDD pour vous (si vous ne l'avez pas déjà créée):
php app/console doctrine:database:create
Pour que Doctrine connaisse nos objets, nous allons créer des fichiers "métadonnées" qui décrivent la façon dont nos objets seront stockés dans la BDD:
# src/Ens/JobeetBundle/Resources/config/doctrine/Category.orm.yml Ens\JobeetBundle\Entity\Category: type: entity table: category id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 255 unique: true oneToMany: jobs: targetEntity: Job mappedBy: category category_affiliates: targetEntity: CategoryAffiliate mappedBy: category
# src/Ens/JobeetBundle/Resources/config/doctrine/Job.orm.yml Ens\JobeetBundle\Entity\Job: type: entity table: job id: id: type: integer generator: { strategy: AUTO } fields: type: type: string length: 255 nullable: true company: type: string length: 255 logo: type: string length: 255 nullable: true url: type: string length: 255 nullable: true position: type: string length: 255 location: type: string length: 255 description: type: text how_to_apply: type: text token: type: string length: 255 unique: true is_public: type: boolean nullable: true is_activated: type: boolean nullable: true email: type: string length: 255 expires_at: type: datetime created_at: type: datetime updated_at: type: datetime nullable: true manyToOne: category: targetEntity: Category inversedBy: jobs joinColumn: name: category_id referencedColumnName: id lifecycleCallbacks: prePersist: [ setCreatedAtValue ] preUpdate: [ setUpdatedAtValue ]
# src/Ens/JobeetBundle/Resources/config/doctrine/Affiliate.orm.yml Ens\JobeetBundle\Entity\Affiliate: type: entity table: affiliate id: id: type: integer generator: { strategy: AUTO } fields: url: type: string length: 255 email: type: string length: 255 unique: true token: type: string length: 255 created_at: type: datetime oneToMany: category_affiliates: targetEntity: CategoryAffiliate mappedBy: affiliate lifecycleCallbacks: prePersist: [ setCreatedAtValue ]
# src/Ens/JobeetBundle/Resources/config/doctrine/CategoryAffiliate.orm.yml Ens\JobeetBundle\Entity\CategoryAffiliate: type: entity table: category_affiliate id: id: type: integer generator: { strategy: AUTO } manyToOne: category: targetEntity: Category inversedBy: category_affiliates joinColumn: name: category_id referencedColumnName: id affiliate: targetEntity: Affiliate inversedBy: category_affiliates joinColumn: name: affiliate_id referencedColumnName: id
Maintenant Doctrine peut générer les classes qui définissent nos objets pour nous avec la commande:
php app/console doctrine:generate:entities EnsJobeetBundle Generating entities for bundle "EnsJobeetBundle" > backing up Job.php to Job.php~ > generating Ens\JobeetBundle\Entity\Job > backing up Category.php to Category.php~ > generating Ens\JobeetBundle\Entity\Category > backing up CategoryAffiliate.php to CategoryAffiliate.php~ > generating Ens\JobeetBundle\Entity\CategoryAffiliate > backing up Affiliate.php to Affiliate.php~ > generating Ens\JobeetBundle\Entity\Affiliate
Si vous regardez dans le répertoire Entity de EnsJobeetBundle, vous trouverez les classes nouvellement créées, là-dedans: Affiliate.php, Category.php, CategoryAffiliate.php et Job.php. Ouvrez Job.php et définissez les valeurs created_at et updated_at comme ci-dessous:
// src/Ens/JobeetBundle/Entity/Job.php // ... public function setCreatedAtValue() { if(!$this->getCreatedAt()) { $this->created_at = new \DateTime(); } } // ... public function setUpdatedAtValue() { $this->updated_at = new \DateTime(); }
Faites de même pour la valeur created_at de la classe Affiliate:
// src/Ens/JobeetBundle/Entity/Affiliate.php // ... public function setCreatedAtValue() { $this->created_at = new \DateTime(); }
Cela rendra Doctrine capable de définir les valeurs created_at et updated_at lors de l'enregistrement ou de la mise à jour des objets. Ce comportement a été défini dans les fichiers Job.orm.yml et Affiliate.orm.yml énumérés ci-dessus.
Nous allons également demander à Doctrine de créer nos tables de BDD (ou les mettre à jour afin de prendre en compte notre configuration) avec la commande:
php app/console doctrine:schema:update --force Updating database schema... Database schema updated successfully! "7" queries were executed
Cette tâche ne doit être vraiment utilisée qu'au cours du développement. Pour une méthode plus stable de mise à jour systématique de votre BDD de production, lisez les migrations Doctrine.
Les tables ont été créées dans la BDD, mais elles ne contiennent pas de données. Pour toute application web, il existe trois types de données: les données initiales (ce qui est nécessaire pour que l'application fonctionne, dans notre cas, nous avons quelques catégories initiales et un utilisateur admin), les données test (nécessaires pour tester l'application) et les données utilisateur (créées par les utilisateurs au fil du temps).
Pour remplir la BDD avec des données initiales, nous allons utiliser DoctrineFixturesBundle. Pour installer ce paquet, suivez les étapes suivantes:
1. Ajoutez les lignes suivantes à votre fichier deps:
[doctrine-fixtures] git=http://github.com/doctrine/data-fixtures.git [DoctrineFixturesBundle] git=http://github.com/doctrine/DoctrineFixturesBundle.git target=/bundles/Symfony/Bundle/DoctrineFixturesBundle version=origin/2.0
2. Mettez à jour les vendors:
php bin/vendors install --reinstall
3. Enregistrez le namespace Doctrine\Common\DataFixtures dans app/autoload.php "avant" Doctrine\Common:
// ... $loader->registerNamespaces(array( // ... 'Doctrine\\Common\\DataFixtures' => __DIR__.'/../vendor/doctrine-fixtures/lib', 'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib', // ... ));
4. Enregistrez le paquet DoctrineFixturesBundle dans app/AppKernel.php:
// ... public function registerBundles() { $bundles = array( // ... new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(), // ... ); // ... }
Maintenant que tout est en place, nous allons créer de nouvelles classes pour charger les données dans un nouveau dossier dans notre paquet: src/Ens/JobeetBundle/DataFixtures/ORM.
// src/Ens/JobeetBundle/DataFixtures/ORM/LoadCategoryData.php namespace Ens\JobeetBundle\DataFixtures\ORM; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Ens\JobeetBundle\Entity\Category; class LoadCategoryData extends AbstractFixture implements OrderedFixtureInterface { public function load(ObjectManager $em) { $design = new Category(); $design->setName('Design'); $programming = new Category(); $programming->setName('Programming'); $manager = new Category(); $manager->setName('Manager'); $administrator = new Category(); $administrator->setName('Administrator'); $em->persist($design); $em->persist($programming); $em->persist($manager); $em->persist($administrator); $em->flush(); $this->addReference('category-design', $design); $this->addReference('category-programming', $programming); $this->addReference('category-manager', $manager); $this->addReference('category-administrator', $administrator); } public function getOrder() { return 1; // the order in which fixtures will be loaded } }
// src/Ens/JobeetBundle/DataFixtures/ORM/LoadJobData.php namespace Ens\JobeetBundle\DataFixtures\ORM; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Ens\JobeetBundle\Entity\Job; class LoadJobData extends AbstractFixture implements OrderedFixtureInterface { public function load(ObjectManager $em) { $job_sensio_labs = new Job(); $job_sensio_labs->setCategory($em->merge($this->getReference('category-programming'))); $job_sensio_labs->setType('full-time'); $job_sensio_labs->setCompany('Sensio Labs'); $job_sensio_labs->setLogo('sensio-labs.gif'); $job_sensio_labs->setUrl('http://www.sensiolabs.com/'); $job_sensio_labs->setPosition('Web Developer'); $job_sensio_labs->setLocation('Paris, France'); $job_sensio_labs->setDescription('You\'ve already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available.'); $job_sensio_labs->setHowToApply('Send your resume to fabien.potencier [at] sensio.com'); $job_sensio_labs->setIsPublic(true); $job_sensio_labs->setIsActivated(true); $job_sensio_labs->setToken('job_sensio_labs'); $job_sensio_labs->setEmail('job@example.com'); $job_sensio_labs->setExpiresAt(new \DateTime('2012-10-10')); $job_extreme_sensio = new Job(); $job_extreme_sensio->setCategory($em->merge($this->getReference('category-design'))); $job_extreme_sensio->setType('part-time'); $job_extreme_sensio->setCompany('Extreme Sensio'); $job_extreme_sensio->setLogo('extreme-sensio.gif'); $job_extreme_sensio->setUrl('http://www.extreme-sensio.com/'); $job_extreme_sensio->setPosition('Web Designer'); $job_extreme_sensio->setLocation('Paris, France'); $job_extreme_sensio->setDescription('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in.'); $job_extreme_sensio->setHowToApply('Send your resume to fabien.potencier [at] sensio.com'); $job_extreme_sensio->setIsPublic(true); $job_extreme_sensio->setIsActivated(true); $job_extreme_sensio->setToken('job_extreme_sensio'); $job_extreme_sensio->setEmail('job@example.com'); $job_extreme_sensio->setExpiresAt(new \DateTime('2012-10-10')); $em->persist($job_sensio_labs); $em->persist($job_extreme_sensio); $em->flush(); } public function getOrder() { return 2; // the order in which fixtures will be loaded } }
Une fois vos fixtures écrites, vous pouvez les charger via la ligne de commande suivante:
php app/console doctrine:fixtures:load
Vous pouvez maintenant vérifier votre BDD, vous devriez voir les données chargées dans les tables.
Le fichier de fixtures des offres fait référence à deux images. Vous pouvez les télécharger (sensio-labs.gif, extreme-sensio.gif) et les mettre dans le répertoire web/uploads/jobs/.
Maintenant, nous allons utiliser un peu de magie! Exécutez à l'invite de commande:
php app/console doctrine:generate:crud --entity=EnsJobeetBundle:Job --route-prefix=ens_job --with-write --format=yml
Cela va créer un nouveau contrôleur src/Ens/JobeetBundle/Controllers/JobController.php avec des actions pour lister, créer, modifier et supprimer des offres (et leurs modèles, formulaires et routes correspondants). Pour le voir dans le navigateur, nous devons importer les nouvelles routes qui ont été créées dans src/Ens/JobeetBundle/Resources/config/routing/job.yml dans notre le fichier de routage principal de notre paquet:
# src/Ens/JobeetBundle/Resources/config/routing.yml EnsJobeetBundle_job: resource: "@EnsJobeetBundle/Resources/config/routing/job.yml" prefix: /job EnsJobeetBundle_homepage: pattern: /hello/{name} defaults: { _controller: EnsJobeetBundle:Default:index }
Nous aurons aussi besoin d'ajouter une méthode __toString() à notre classe Category pour être utilisée par le menu déroulant Catégorie du formulaire de modification d'offre:
// src/Ens/JobeetBundle/Entity/Category.php // ... public function __toString() { return $this->getName(); }
Videz le cache:
php app/console cache:clear --env=prod php app/console cache:clear --env=dev
Vous pouvez maintenant tester le contrôleur Job dans un navigateur: http://jobeet.local/job/ ou, dans l'environnement de développement, http://jobeet.local/app_dev.php/job/
Vous pouvez désormais créer et modifier des offres. Essayez de laisser un champ obligatoire vide, ou essayez d'entrer une date invalide. C'est vrai, Symfony a créé des règles de validation de base par introspection du schéma de BDD.
Nous avons à peine écrit du code PHP, mais nous avons un module web de travail pour le modèle Job, prêt à être modifié et personnalisé. Rappelez-vous : pas de code PHP signifie pas de bugs!