Après une première partie introductive au Framework Symfony, on se retrouve aujourd’hui pour commencer à coder !
Nous verrons dans cette deuxième partie la configuration des packages de notre projet ainsi que la mise en place et l’utilisation des Controller / Managers / Service / RepositoryC'est l'emplacement où Git va stocker toutes les informations permettant de gérer le versioning et l'historisation d'une application. et Entity.
La configuration des packages du projet se base sur le projet initialisé lors de la partie 1 de cette série d’articles. Si vous rencontrez quelques difficultés, nous vous conseillons de vous y référer.
Lors de l’ajout de certains packages à l’aide de la commande composer require monpackage
, SymfonyFramework PHP permettant de développer des applications web. s’occupe de créer les fichiers YAML de configuration du package, d’activer des bundles et de modifier les variables d’environnement.
Certains packages ajoutés nécessitent l’activation de bundles dans le fichier bundles.php dans le dossier config de notre projet.
Prenons comme exemple l’installation du package profiler-pack (qui vous sera extrêmement utile pendant votre développement).
Tout d’abord il faut ajouter le package au projet :
composer require symfony/profiler-pack
Une fois l’installation terminée, le fichier bundle.php a été modifié pour ajouter 2 nouveaux bundles :
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => [’all’ => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => [’all’ => true], // new
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => [’dev’ => true, ’test’ => true], // new
];
On peut remarquer une différence entre le TwigBundle et le WebProfilerBundle :
[’all’ => true]
: Le TwigBundle est accessible dans n’importe quel environnement de développement. Il permet d’utiliser le moteur de template Twig et est donc toujours nécessaire pour afficher les pages web.[’dev’ => true, ’test’ => true]
: Le WebProfilerBundle est accessible uniquement pour les environnements de test et de dev. Ce bundle nous servant à déboguer notre application lors du développement, il ne doit pas être accessible pour l’environnement de production.Il est donc possible de choisir l’activation du bundle en fonction de l’environnement désiré.
Ce fichier, à la racine du projet, stocke les variables d’environnement nécessaires au fonctionnement de l’application. Certains packages modifient le fichier pour y ajouter leurs variables.
Prenons comme exemple l’installation du package orm-pack (qui nous permet la connexion à la BDD et l’installation de Doctrine).
Tout d’abord, il faut ajouter le package au projet :
composer require symfony/orm-pack
Une fois l’installation terminée, le fichier .env a été modifié pour ajouter la variable d’environnement DATABASE_URL :
...
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
###< doctrine/doctrine-bundle ###
...
Il vous restera ensuite à renseigner vos identifiants de connexion à la base de données.
Mais où est utilisée cette variable ? Il faut, pour le savoir, se rendre dans le dossier packages du dossier config.
Pour chaque package ajouté nécessitant une configuration, un fichier YAML de config est créé dans ce dossier.
Lors de l’ajout du premier package profiler-pack, un fichier web_profiler.yaml a été créé.
Pour la package orm-pack, ce sont les fichiers doctrine.yaml et doctrine_migration.yaml qui ont été créés. Nous allons nous intéresser au fichier doctrine.yaml :
doctrine:
dbal:
url: ’%env(resolve:DATABASE_URL)%’
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: ’5.7’
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: ’%kernel.project_dir%/src/Entity’
prefix: ’App\Entity’
alias: App
Nous retrouvons notre variable DATABASE_URL qui est utilisée pour se connecter à la BDD. On remarquera que pour récupérer une variable dans le fichier .env, il faut utiliser la syntaxe %env(resolve:ma_variable)%
.
Il vous est ensuite possible de configurer la connexion à la BDD selon vos besoins, et surtout spécifier où se situent vos Entity qui sont utilisés pour faire le lien avec les tables de votre BDD.
Enfin, on remarquera que la configuration des fichiers YAML peut être adapté selon votre environnement. Le fichier doctrine.yaml est utilisé une seconde fois dans le dossier prod, permettant ainsi d’ajouter une configuration spécifique à l’environnement de production.
Dans l’exemple que nous avons préparé, nous allons voir comment récupérer une liste d’utilisateurs en BDD.
Nous allons créer notre classe User dans notre fichier User.php du dossier Entity :
<?php
namespace App\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User
{
/**
* @var integer
*
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var ?string
*
* @ORM\Column(name="first_name", type="string")
*/
private $firstName;
/**
* @var ?string
*
* @ORM\Column(name="last_name", type="string")
*/
private $lastName;
/**
* @var ?DateTime
*
* @ORM\Column(name="user_birthday", type="datetime")
*/
private $birthday;
/** @var ?string */
private $description;
/** @return int */
public function getId(): int {
return $this->id;
}
/** @param int $id */
public function setId(int $id): void {
$this->id = $id;
}
/** @return string|null */
public function getFirstName(): ?string {
return $this->firstName;
}
/** @param string|null $firstName */
public function setFirstName(?string $firstName): void {
$this->firstName = $firstName;
}
/** @return string|null */
public function getLastName(): ?string {
return $this->lastName;
}
/** @param string|null $lastName */
public function setLastName(?string $lastName): void {
$this->lastName = $lastName;
}
/** @return DateTime|null */
public function getBirthday(): ?DateTime {
return $this->birthday;
}
/** @param DateTime|null $birthday */
public function setBirthday(?DateTime $birthday): void
{
$this->birthday = $birthday;
}
/** @return string|null */
public function getDescription(): ?string {
return $this->description;
}
/** @param string|null $description */
public function setDescription(?string $description): void {
$this->description = $description;
}
}
Nous voyons que des décorateurs ont été utilisés pour la classe et nos 4 attributs :
Décorateur | Explication |
---|---|
@ORM\Entity | Spécifie que notre classe est une Entity. Il est possible d’y rattacher un Repository (nous y reviendrons). |
@ORM\Table(name="user") | Spécifie le nom de la table en BDD (facultatif). |
@ORM\Id | Spécifie l’attribut comme PK de la table. |
@ORM\GeneratedValue | Spécifie la PK comme étant un auto-incrément. |
@ORM\Column | Spécifie principalement le nom et le type d’une colonne en BDD. Il est possible de rajouter les valeurs par défaut, longueur minimum / maximum et si le champ peut être NULL en BDD selon nos besoins. |
Une fois notre UserEntity créé, la structure de notre BDD doit être mise à jour.
Pour cela, il nous faudra générer un fichier de migration et l’exécuter pour appliquer la création de la table user.
La commande suivante permet la création d’un fichier de migration contenant le SQLLangage permettant de communiquer avec une base de données. appliqué en BDD :
php bin/console doctrine:migrations:diff
Un fichier Version[datetime].php est créé dans le dossier migrations. Tous les fichiers de migration sont triés par date pour pouvoir les exécuter les un après les autres dans le bon ordre.
Pour appliquer le fichier de migration, il suffit d’exécuter la commande :
php bin/console doctrine:migrations:execute --up DoctrineMigrations\Version[datetime]
La table user est créée en BDD !
Si vous voulez annuler l’insertion de la table, vous pouvez tout à fait utiliser la commande :
php bin/console doctrine:migrations:execute --down DoctrineMigrations\Version[datetime]
Il faut tout d’abord installer le package doctrine-fixtures-bundle :
composer require --dev doctrine/doctrine-fixtures-bundle
Ensuite, pour générer de fausses données utilisateur, un package existe et le fera à notre place :
composer require --dev fzaninotto/faker
Dans le fichier AppFixtures.php du dossier DataFixtures, nous allons créer 20 utilisateurs :
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;
use Exception;
class AppFixtures extends Fixture
{
/**
* @param ObjectManager $manager
* @throws Exception
*/
public function load(ObjectManager $manager)
{
$faker = Factory::create();
// création de 20 utilisateurs
for ($i = 0; $i < 20; $i++) {
$user = new User();
$user->setFirstName($faker->firstName);
$user->setLastName($faker->lastName);
$user->setBirthday($faker->dateTime);
$manager->persist($user);
}
$manager->flush();
}
}
Il nous reste plus qu’à insérer les fixtures en BDD :
php bin/console doctrine:fixtures:load
Nous allons générer un fichier UserRepository.php dans le dossier Repository avec un exemple de queryBuilder :
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
/**
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* @param \DateTime $datetime
* @return User[]
*/
public function findByBirthdayMoreThan(\DateTime $datetime) {
return $this->createQueryBuilder(’u’)
->where(’u.birthday > :datetime’)
->setParameter(’datetime’, $datetime)
->getQuery()
->getResult()
;
}
}
Il faut savoir qu’un Repository doit être le seul à accéder à la BDD au travers de queryBuilder ou requêtes natives.
Enfin, il ne faut surtout pas oublier de lier le Repository avec l’Entity User, sinon impossible d’aller requêter sur la table user :
<?php
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository") // ajouter le Repository
* @ORM\Table(name="user")
*/
class User
Nous allons créer un fichier UserService.php dans le dossier Service avec une fonction qui va modifier la description des utilisateurs :
<?php
namespace App\Service;
use App\Entity\User;
class UserService {
/**
* @param User $user
* @return User
*/
public function updateUserDescription(User $user) {
$description = ’Je suis ’ . ucfirst($user->getFirstName()) . ’ ’ .
mb_strtoupper($user->getLastName()) . ’, mon anniversaire est le ’ .
$user->getBirthday()->format(’d/m/Y’);
$user->setDescription($description);
return $user;
}
}
Il faut savoir qu’un Service va s’occuper de tout le traitement des données reçues depuis un repository.
Nous allons créer un fichier UserManager.php dans le dossier Manager avec des fonctions qui iront chercher un seul ou tous les utilisateurs avec leur description :
<?php
namespace App\Manager;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
class UserManager
{
/** @var EntityManagerInterface */
private $em;
/** @var UserService */
private $userService;
function __construct(EntityManagerInterface $em, UserService $userService) {
$this->em = $em;
$this->userService = $userService;
}
/**
* Rechercher d’un utilisateur avec sa description
*
* @param int $id
* @return User|false
* @throws Exception
*/
public function findOneWithDescription(int $id) {
$user = $this->getRepo()->find($id);
if (!$user) {
throw new Exception(’Utilisateur introuvable !’, 422);
}
return $this->userService->updateUserDescription($user);
}
/**
* Liste des utilisateurs avec leur description
*
* @return User[]
*/
public function findAllWithDescription() {
$usersWithDescription = [];
$users = $this->getRepo()->findAll();
foreach ($users as $user) {
$usersWithDescription[] = $this->userService->updateUserDescription($user);
}
return $usersWithDescription;
}
/**
* Récupération de notre Repository UserRepository
*
* @return UserRepository
*/
public function getRepo(): UserRepository
{
return $this->em->getRepository(User::class);
}
}
Il faut savoir qu’un Manager sert seulement à rediriger depuis un Controller vers un Service ou directement un Repository.
Dans cette seconde partie, nous verrons comment utiliser un Controller et renvoyer une réponse en JSON, à la manière dont le ferait l’application si celle-ci était une API. L’utilisation des templates Twig se fera dans une prochaine partie.
Pour pouvoir transformer nos objets User en JSON, nous devons rajouter un Serializer à notre application. Il suffit d’utiliser la commande suivante :
composer require symfony/serializer symfony/property-access
Nous allons créer un fichier UserController.php dans le dossier Controller et ajouter 2 points d’entrée :
<?php
namespace App\Controller;
use App\Manager\UserManager;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class UserController extends AbstractController
{
/** @var Serializer */
private $serializer;
function __construct() {
$this->serializer = new Serializer(
[new DateTimeNormalizer([’datetime_format’ => ’Y-m-d\TH:i:s.u\Z’]), new ObjectNormalizer()],
[’json’ => new JsonEncoder()]
);
}
/**
* @Route("/users", name="get-users", methods={"GET"})
*
* @param UserManager $userManager
* @return Response
*/
public function getUsers(UserManager $userManager)
{
$users = $userManager->findAllWithDescription();
return new Response($this->serializer->serialize($users, ’json’));
}
/**
* @Route("/users/{id}", name="get-user", requirements={"id"="\d+"}, methods={"GET"})
*
* @param UserManager $userManager
* @param int $id
* @return Response
* @throws Exception
*/
public function getOneUser(UserManager $userManager, int $id)
{
$user = $userManager->findOneWithDescription($id);
return new Response($this->serializer->serialize($user, ’json’));
}
}
Il existe plusieurs façon de récupérer nos utilisateurs :
Voici un exemple de JSON qui doit être retourné si l’on accède au service http://localhost:8000/users/1 de notre application Symfony 5 :
{
"id": 1,
"firstName": "Fidel",
"lastName": "Sipes",
"birthday": "2020-05-30T02:31:10.000000Z",
"description": "Je suis Fidel SIPES. Mon anniversaire est le 30\/05\/2020"
}
La prochaine partie nous amènera sur le fonctionnement des assets, du moteur de template Twig et des Forms Symfony. Nous verrons, entre autres, comment lister et modifier nos utilisateurs.
À BIENTÔT ♥
Affichage et administration des différents utilisateurs d’une application Symfony
Zoom sur Next.js, l'étoile montante des frameworks JavaScript
Spring Security : c’est quoi et comment ça marche ? Cet article passe en revue les fondamentaux à connaître