Annexe TP n°1 - Construction de l’appli Todo avec Symfony
Construction de l’application « from scratch »
Table des matières
- 1. Introduction
- 2. Création d’un projet Symfony grâce à Composer
- 3. Ajout des modules PHP utiles
- 4. Ajout de l’entité principale : « Todo »
- 5. Configuration de la base de données de tests dans l’environnement de développement
- 6. Ajout de l’interface console
- 7. Ajout de l’affichage de la liste des tâches
- 8. Ajout de Todo::__toString()
- 9. Ajout d’une interface EasyAdmin avec un contrôleurs CRUD pour Todo
- 10. Customisation de EasyAdmin dans ToDo
1. Introduction
Cette annexe documente l’essentiel des étapes utilisées pour réaliser la version 2.x de l’application Todo telle que vue dans la séance de TP.
Inutile de tester cela, sauf si vous avez vraiment l’intention de repartir d’une feuille blanche pour un projet personnel.
Cette procédure est fournie sans garantie d’exhaustivité.
2. Création d’un projet Symfony grâce à Composer
Vous allez utiliser la commande new
de l’outil Symfony en ligne de
commande (Symfony CLI), qui va créer le répertoire de développement
d’une application Symfony et y télécharger le code source d’un
squelette d’application.
Cette commande s’appuie sur le gestionnaire de paquetages PHP
Composer, en appelant la commande create-project
de composer.
Nous avons choisi de privilégier la version LTS de Symfony pour les
TP afin de stabiliser les contenus sur plusieurs années. Néanmoins
vous devriez pouvoir exécuter à peu près les mêmes opérations sur
la version stable de Symfony. Modifiez la version en choisissant
plutôt --version=stable
ci-dessous, si vous préférez tenter l’aventure.
Téléchargez et installez le squelette d’application standard de Symfony, sur lequel vous allez travailler :
cd $HOME/CSC4101/tp-01/ symfony --debug new --no-git mytodo --version=lts
Cette commande affiche plusieurs infos intéressantes pour comprendre ce qui se passe :
* Creating a new Symfony 6.4 project with Composer ... [OK] Your project is now ready in /.../mytodo
Composer a téléchargé dans un nouveau sous-répertoire mytodo/
le squelette d’un projet d’application
Symfony, pour une version précise de Symfony, ici la 6.4. C’est celle
qui correspond à la
version LTS (Long-Term Support) de Symfony
(au moment où on exécute la commande symfony new --version=lts
).
3. Ajout des modules PHP utiles
Nous aurons besoin de différents modules pour permettre à l’application de fonctionner, ou pour nous aider dans le prototypage de cette application, notamment :
- Doctrine
- le composant d’accès à la base de données via des objets PHP
- le « Maker bundle »
- qui va nous permettre de générer du code PHP plutôt que d’avoir à tout coder à la main
- les « DataFixtures »
- qui permettent de charger automatiquement des données de test dans la base de données
Procédez à l’installation avec les commandes suivantes à l’intérieur du répertoire mytodo
:
cd mytodo/
Monolog :
symfony composer require symfony/monolog-bundle
ORM Doctrine :
symfony composer require -n symfony/orm-pack
« Maker bundle » :
symfony composer require --dev -n symfony/maker-bundle
DataFixtures :
symfony composer require --dev doctrine/doctrine-fixtures-bundle
Composer a téléchargé et extrait dans vendor/
les différentes
bibliothèques PHP. On peut donc les utiliser dans le code de l’application.
4. Ajout de l’entité principale : « Todo »
Si vous êtes comme la plupart des programmeurs, vous aimez modérément
copier-coller du code qui pourrait être généré automatiquement.
Ça tombe bien, Symfony propose différent assistants qu’on ne va pas se
priver d’utiliser pour générer la base du code de nos applications,
plutôt que d’écrire nous-même du code buggé.
Lancez l’assistant make:entity
, dans le terminal, depuis l’intérieur
du projet Symfony. Il va nous servir à générer le code de classes PHP
pour gérer notre entité « Todo » du modèle de données.
Répondez aux questions de l’assistant pour obtenir une interaction similaire à la trace présentée ci-dessous :
symfony console make:entity
Class name of the entity to create or update (e.g. VictoriousPuppy): > Todo created: src/Entity/Todo.php created: src/Repository/TodoRepository.php Entity generated! Now let's add some fields! You can always add more fields later manually or by re-running this command. New property name (press <return> to stop adding fields): > title Field type (enter ? to see all types) [string]: > Field length [255]: > Can this field be null in the database (nullable) (yes/no) [no]: > yes updated: src/Entity/Todo.php Add another property? Enter the property name (or press <return> to stop adding fields): > completed Field type (enter ? to see all types) [string]: > ? Main Types * string * text * boolean * integer (or smallint, bigint) * float Relationships/Associations * relation (a wizard 🧙 will help you build the relation) * ManyToOne * OneToMany * ManyToMany * OneToOne Array/Object Types * array (or simple_array) * json * object * binary * blob Date/Time Types * datetime (or datetime_immutable) * datetimetz (or datetimetz_immutable) * date (or date_immutable) * time (or time_immutable) * dateinterval Other Types * ascii_string * decimal * guid Field type (enter ? to see all types) [string]: > boolean Can this field be null in the database (nullable) (yes/no) [no]: > updated: src/Entity/Todo.php Add another property? Enter the property name (or press <return> to stop adding fields): > created Field type (enter ? to see all types) [string]: > ? Main Types * string * text * boolean * integer (or smallint, bigint) * float Relationships/Associations * relation (a wizard 🧙 will help you build the relation) * ManyToOne * OneToMany * ManyToMany * OneToOne Array/Object Types * array (or simple_array) * json * object * binary * blob Date/Time Types * datetime (or datetime_immutable) * datetimetz (or datetimetz_immutable) * date (or date_immutable) * time (or time_immutable) * dateinterval Other Types * ascii_string * decimal * guid Field type (enter ? to see all types) [string]: > datetime Can this field be null in the database (nullable) (yes/no) [no]: > yes updated: src/Entity/Todo.php Add another property? Enter the property name (or press <return> to stop adding fields): > updated Field type (enter ? to see all types) [string]: > datetime Can this field be null in the database (nullable) (yes/no) [no]: > yes updated: src/Entity/Todo.php Add another property? Enter the property name (or press <return> to stop adding fields): > Success! Next: When you're ready, create a migration with symfony console make:migration
L’assistant a créé pour nous deux classes PHP qui utilisent Doctrine :
src/Entity/Todo.php
: gère les instances en mémoire des tâchessrc/Repository/TodoRepository.php
: gère le chargement des tâches depuis la base de données
Chargez le projet dans votre IDE, pour voir apparaître le code de ces deux classes.
Attention à lire attentivement les questions (ainsi que les messages en réponse), et à éviter le copier/coller un peu rapide.
5. Configuration de la base de données de tests dans l’environnement de développement
Dans l’environnement de développement, on va effectuer des tests sur le SGBD SQLite. Il faut donc configurer cela?
Chargez le le fichier
.env
dans l’IDEAttention, c’est un fichier caché… il faut éventuellement le faire apparaître dans l’IDE.
Pour cela, dans Eclipse :
- cliquez sur le bouton avec trois petits points verticaux, en haut à droite de la colonne du « Project Explorer »;
- puis sur « Filters and Customizations »;
- dans la liste des « Pre-Set filters », dé-cochez «
.* ressources
», puis « OK ».
Les fichiers cachés apparaissent dans le projet, dont le fichier
.env
.Ouvrez
.env
, et vérifiez la valeur de la variableDATABASE_URL
.Elle doit typiquement prendre la valeur
sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db
, afin d’utiliser SQLite pour les besoins du développeur qui teste en local :DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
Ainsi, la base de données SQLite utilisée pour les tests sera créée dans
$HOME/CSC4101/tp-01/mytodo/var/data_dev.db
.Créez un fichier de base de données SQLite (vide), en exécutant la commande suivante :
symfony console doctrine:database:create
Créez le schéma de la base SQLite (tables, index, etc.), en utilisant la sous-commande
doctrine:schema:create
:symfony console doctrine:schema:create
Ajoutez des données de test :
symfony console dbal:run-sql "INSERT INTO todo (TITLE, COMPLETED) VALUES ('Se passer de ChatGPT', false)"
6. Ajout de l’interface console
Cette étape va permettre d’ajouter une couche de présentation (dans la console d’un terminal) dans notre application.
6.1. Ajout de la commande app:list-todos
Ajoutons une interface en ligne de commande à notre application PHP,
pour disposer d’une commande accessible pour le développeur via
l’interface principale offerte par bin/console
.
Cette fois encore, utilisons un assistant générateur de code pour nous faciliter le travail :
symfony console make:command
Choose a command name (e.g. app:victorious-popsicle): > app:list-todos created: src/Command/ListTodosCommand.php Success! Next: open your new command class and customize it! Find the documentation at https://symfony.com/doc/current/console.html
Consultez le résultat généré dans src/Command/ListTodosCommand.php
Vérifiez que la commande est bien disponible via :
symfony console list app
et :
symfony console app:list-todos --help
Et enfin qu’elle répond quand on l’invoque :
symfony console app:list-todos [OK] You have a new command! Now make it your own! Pass --help to see your options.
Le squelette de code de commande est là, prêt à recevoir notre code applicatif.
6.2. Branchement de l’interface à la base de données
Pour que notre commande puisse afficher la liste des tâches on va devoir réaliser quelques branchements entre notre code et celui de Doctrine, qui gère l’accès aux données.
On va modifier le code de src/Command/ListTodosCommand.php
, pour ajouter
un attribut todoRepository
dans la classe ListTodosCommand
, permettant
de gérer l’accès à la base de données via le composant Doctrine.
Copiez-collez les éléments ci-dessous pour ajouter l’attribut
todoRepository
, et son initialisation dans le constructeur de ListTodosCommand
(attention à ne pas
dupliquer la déclaration de la classe) :
// ... use App\Entity\Todo; use App\Repository\TodoRepository; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Console\Attribute\AsCommand; // ... class ListTodosCommand extends Command { /** * @var TodoRepository data access repository */ private $todoRepository; /** * Plugs the database to the command * * @param ManagerRegistry $doctrineManager */ public function __construct(ManagerRegistry $doctrineManager) { $this->todoRepository = $doctrineManager->getRepository(Todo::class); parent::__construct(); } // ...
7. Ajout de l’affichage de la liste des tâches
Ajoutons maintenant au code de la commande, qui a été généré par l’asssistant, les instructions permettant de charger toutes les tâches présentes dans la base de données.
On utilise la méthode findAll()
du repository
Doctrine des tâches TodoRepository
, et qui renvoie un
tableau de tâches. On peut manipuler ce tableau comme un tableau PHP
ordinaire, par exemple avec foreach
.
Modifiez la méthode execute()
de ListTodosCommand
:
protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); // récupère une liste toutes les instances de la classe Todo $todos = $this->todoRepository->findAll(); //dump($todos); if(!empty($todos)) { $io->title('list of todos:'); $io->listing($todos); } else { $io->error('no todos found!'); return Command::FAILURE; } return Command::SUCCESS; }
Testez avec :
symfony console app:list-todos
Le programme échoue avec une erreur (attendue) du type :
symfony console app:list-todos list of todos: ============== In SymfonyStyle.php line 107: Object of class App\Entity\Todo could not be converted to string app:list-todos [--option1] [--] [<arg1>]
Ce genre de chose devrait vous paraître cohérent avec ce qui a été vu l’année passée en programmation orientée objet.
La méthode listing()
de SymfonyStyle
essaie d’afficher une chaîne de caractères sur
la console… mais une instance de la classe Todo
peut-elle être
convertie en chaîne de caractères ? Pas de base, en PHP.
Réglons cela.
8. Ajout de Todo::__toString()
Ajoutez, dans src/Entity/Todo.php
, la méthode de conversion de tâche
en chaîne de caractère qui nous manque : Todo::__toString()
.
Par exemple :
public function __toString() { $s = ''; $s .= $this->getId() .' '. $this->getTitle() .' '; $s .= $this->isCompleted() ? '(completed)': '(not complete)'; return $s; }
Réessayez le lancement de notre commande…
Bravo, vous avez une application PHP objet qui effectue des requêtes en base de données et affiche le résultat sur la console. La classe !
9. Ajout d’une interface EasyAdmin avec un contrôleurs CRUD pour Todo
Vous allez ajouter un tableau de bord d’administration fonctionnant grâce à EasyAdmin.
9.1. Ajout du contrôleur d’administration principal
Pour cela, déroulez les instructions suivantes (en vous référant à la documentation https://symfony.com/bundles/EasyAdminBundle/current/index.html pour plus de détails) :
ajout du bundle EasyAdmin au projet, avec Composer :
cd $HOME/CSC4101/projet/[nom-code] symfony composer req admin
création du controleur du tableau de bord d’administration
src/Controller/Admin/DashboardController.php
:symfony console make:admin:dashboard
nettoyez le cache du code précompilé de Symfony
symfony console cache:clear
lancez le serveur Web de l’environnement de développement Symfony :
symfony server:start
- Testez dans le navigateur qu’une page « Welcome to EasyAdmin 4 » s’affiche bien sur l’URL
/admin
(sur http://locahost:8000/admin par exemple).
9.2. Ajout du contrôleur CRUD pour Todo
Vous allez ajouter au tableau de bord d’administration un premier contrôleur « CRUD » d’EasyAdmin qui servira à afficher et modifier les entités Todo.
création du contrôleur de gestion CRUD des Todo
src/Controller/Admin/TodoCrudController.php
:symfony console make:admin:crud
réalisez ensuite le « câblage » de
TodoCrudController
dans l’affichage de la page principaleModifiez le code de la méthode
DashboardController::index()
pour générer une redirection vers la route du CRUD de Todo :public function index(): Response { // Option 1. You can make your dashboard redirect to some common page of your backend // // 1.1) If you have enabled the "pretty URLs" feature: return $this->redirectToRoute('admin_todo_index'); //[...] }
Testez que ça fonctionne : le chargement de http://localhost:8000/admin/ doit rediriger vers le tableau de bord CRUD de Todo, sur http://localhost:8000/admin/todo
- Testez que ce tableau de bord CRUD fonctionne et que les modifications sont possibles en base de données pour des tâches.
10. Customisation de EasyAdmin dans ToDo
Une fois qu’on a effectué les opérations de base pour ajouter EasyAdmin dans le code, il pourrait être intéressant de modifier certains paramètres pour « customiser » l’affichage des Todo.
Pour cela, on devra probablement surcharger certains gabarits d’affichage.
Nous ne détaillons pas toutes les étapes éventuellement requises, car elles ont un intérêt padagogique limité à ce stade.
Pour info, voici un exemple de TodoCrudController::configureFields()
avec quelques commentaires pour expliciter le rôle des différents
paramètres.
On trouvera le code des gabarits qui n’a pas été reproduit ici dans
les sources du projet Todo v2.* finalisé (dans templates/admin/
).
public function configureFields(string $pageName): iterable { return [ // Id shouldn't be modified IdField::new('id')->hideOnForm(), // Completed will be rendered as a toggle only in edit BooleanField::new('completed') ->onlyOnForms() ->hideWhenCreating(), // otherwise it may be displayed on index as a string (as an Integer since Text won't be able to convert bool to string) IntegerField::new('completed') ->onlyOnIndex() ->formatValue(function ($value) { return ($value ? 'COMPLETED' : 'ACTIVE'); }), // Title will be rendered so as to include a link, and be striked whenever completed TextField::new('title') ->setTemplatePath('admin/fields/todo_index_title.html.twig'), DateField::new('created'), DateField::new('updated'), ]; } public function configureActions(Actions $actions): Actions { // For whatever reason show isn't in the menu, bu default return $actions ->add(Crud::PAGE_INDEX, Action::DETAIL) ; } public function configureCrud(Crud $crud): Crud { // Customize the rendering of the row to grey-out the completed Todos return $crud ->overrideTemplate('crud/index', 'admin/crud/todo_index.html.twig') ; }
D’autres paramétrages un peu spéciaux ont dû être faits pour éviter certains problèmes de mise au point (ou contourner des bugs) :
Dans config/packages/framework.yaml
, modifier la valeur de
cookie_secure
à false
:
framework: # ... session: # ... # make EasyAdmin happy, otherwise CSRF fails for weird reason cookie_secure: false