Symfony3 et Bananas
Kevin Verschaeve
Quoi qu'on va faire ?
- Découvir ce qu'est un Framework
- Voir ce qu'est le MVC
- Voir Composer
- Installer Symfony (youhou!)
- Faire notre premier projet
- Voir les bases de Symfony
- Boire une bière ?
C'est quoi un framework ?
« Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony. »
-
Une structure
-
Des composants
-
Une manière de coder
- Routing
- Sécurité
- Bundles
- ...
Une structure
Des composants
35 composants
Dont:
- Console
- Debug
- Form
- HttpKernel
- Routing
- Security
- VarDumper
- ...
Une manière de coder
- Design patterns
- MVC
- Separation of concerns
- Coding Style (PSR-2)
Composer
Gestionnaire de dépendances
"J'ai besoin de cette lib, qui elle même a besoin de telle lib, qui elle même a besoin de telle lib, qui ... "
$ composer install
C'est tout !
Enfin presque
On déclare les librairies dont on a besoin dans le fichier composer.json à la racine du projet
{
"require": {
"pop/lalib": "~1.0"
}
}
La commande composer install, va télécharger la librairie "pop/lalib" en version 1.0 minimum et toutes les dépendances de celle ci, puis l'installer dans le projet
Là c'est tout
Mais y'a encore pleins de trucs cool à faire avec composer
C'est maintenant qu'on bosse !
Installer Symfony
Installer l'installateur ....
$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
$ sudo chmod a+x /usr/local/bin/symfony
Pour les gens biens (sous Linux) et les riches (Mac OS X)
=> nouvelle commande :
$ symfony
Pour ceux qui cherchent les problèmes (sous Windows)
c:\> php -r "readfile('https://symfony.com/installer');" > symfony
c:\> move symfony c:\projects
=> nouvelle commande :
c:\projects\> php symfony
Créer un projet
$ symfony new pop 3.0
Créé un projet Symfony, avec la version 3.0 dans le dossier "pop"
$ cd pop
$ php bin/console server:run
Va dans le dossier du projet et lance un serveur
Dans le navigateur, aller sur http://localhost:8000
Ca marche !
La structure du projet
app/
-> fichiers de configurations, vues, ...
src/
-> le code PHP de l'application, on passe la majeure partie du temps dans ce dossier
bin/
-> fichiers exécutables, comme bin/console
tests/
-> test automatisés
var/
-> fichiers créés automatiquement (logs, cache)
vendor/
-> librairies/bundles externes téléchargés par composer
web/
-> root du projet, ici va tout ce qui est publique, css, js, images...
Configuration
Configurer une base de données
Activer la traduction et régler la langue par défaut
# app/config/config.yml
# sous la clé parameters, régler la clé locale sur la langue souhaitée (ici fr)
parameters:
locale: fr
# décommenter la ligne "translator" sous la clé "framework"
framework:
translator: { fallbacks: ["%locale%"] }
# app/config/parameters.yml
parameters:
database_host: 127.0.0.1
database_port: null
database_name: pop
database_user: root
database_password: pass
Les Bundles !!
J'ai pas envie de faire ce truc, y'a pas quelqu'un qui l'aurais déjà fait ?
Surement !
Et il en a surement aussi fait un bundle
$ composer require lexik/maintenance-bundle
Installer un bundle
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Lexik\Bundle\MaintenanceBundle\LexikMaintenanceBundle(),
);
// ...
}
Enregistrer le bundle pour Symfony dans app/AppKernel.php
Configurer le bundle
lexik_maintenance:
driver:
class: '\Lexik\Bundle\MaintenanceBundle\Drivers\ShmDriver'
Dans app/config/config.yml
Tester
# passer le site en mode maintenance
$ bin/console lexik:maintenance:lock
# passer le site en mode normal
$ bin/console lexik:maintenance:unlock
Controller et actions
-
Un controller contient un ensemble d'Actions
-
La logique métier est écrite dans les controllers
-
Le nom des Actions se termine par 'Action', ex: showAction()
-
Les actions sont atteintes via une route et retournent obligatoirement un objet de type Response
-
Une Response est ce qui est affiché à l'utilisateur
-
Une action doit être la plus courte possible (moins de 10 lignes)
La plus part du temps, une Response est une vue (du code HTML, ici compilé avec Twig)
Mais ca peut aussi être une redirection, un stream, du JSON, du Binaire ou du custom
Le Routing
Une route :
-
Fait la liaison entre une URL et une Action
-
À un nom unique
-
À un ordre déterminant
-
Peut prendre des paramètres (entre {}) et des valeurs par défaut
-
Peut avoir des restrictions
-
Peut être sécurisée
Twig
"Moteur de templates"
Permet de rendre du PHP dans du HTML de façon plus triviale, propre, sécurisée et facile à comprendre pour un intégrateur
avec PHP
<h1>Hello <?php echo $name; ?></h1>
avec Twig
<h1>Hello {{ name }}</h1>
Syntaxe de base
avec PHP
<ul>
<?php foreach ($articles as $article): ?>
<li><?php echo $article->getName(); ?></li>
<?php endforeach; ?>
</ul>
avec Twig
<ul>
{% for article in articles if article.name is not empty %}
<li>{{ article.name }}</li>
{% else %}
Ya pas d articles
{% endfor %}
</ul>
Foreach
avec PHP
<h1>Hello <?php echo strtoupper($name); ?></h1>
<p>Liste: <?php echo implode(', ', $liste); ?></p>
avec Twig
<h1>Hello {{ name|upper }}</h1>
<p>Liste: {{ liste|join }}</p>
Les filtres
Héritage de templates
{# index.html.twig #}
{% extends '::base.html.twig' %}
{% block title %}Index{% endblock %}
{% block body %}
Hello {{ name }} !
{% endblock %}
{# base.html.twig #}
<html>
<head>
<title>{% block title %}Fichier de base{% endblock %}</title>
</head>
<body>
{% block body %}
Le contenu par défaut
{% endblock %}
</body>
</html>
Plein d'autres trucs
- Includes
- Macros
- Auto escaping
- Comparaisons
- Extensions
- ...
Base de données
Créer la base de données
$ php bin/console doctrine:database:create
Créer une entité
$ php bin/console doctrine:generate:entity
# AppBundle:Article
# annotation
# title, string 255, true, false
# author, string 255, false, false
$ php bin/console doctrine:schema:create
Requêter
Doctrine utilise des Manager pour gérer nos entités
Ces managers fournissent des Repository qui eux fournissent des méthodes clés en main pour requeter la base de données
# dans une action de controller
public function monAction()
{
$articleRepository = $this->getDoctrine()->getRepository(Article::class);
$articles = $articleRepository->findAll();
// ce qu'on veut, avec la liste d'articles
}
$repository->findAll(); // retourne toutes les entités
$repository->find(123); // retourne l'entité avec l'id 123
$repository->findBy(['author' => 'Kevin']); // retourne la ou les entités dont l'auteur == 'Kevin'
$repository->findOneBy(...); // identique à la précédente, excepté qu'ici on ne retourne que le premier résultat
$repository->findOneByName('Kevin'); // identique à la précédente, en passant par des méthodes magiques
Quelques méthodes fournies par Doctrine
Que faire si on souhaite des requêtes plus complexes ?
Un repository custom
<?php
namespace AppBundle\Repository;
class ArticleRepository extends EntityRepository
{
public function findFirstArticlesOfAuthor($author, $nbArticles)
{
return $this->createQueryBuilder('a')
->where('a.author = :author')
->setParameter('author', $author)
->setMaxResults($nbArticles)
->getQuery()
->getResult()
;
}
}
Le repository
Lier le repository à l'entité
/**
* @ORM\Entity(repositoryClass="AppBundle\Repository\ArticleRepository")
*/
class Article
{
// ...
}
public function monAction()
{
// ...
$myFirst5Articles = $this
->getDoctrine()
->getRepository(Article::class)
->findFirstArticlesOfAuthor('Kevin', 5)
;
// ...
}
Pour requêter, ex dans un controller
Les formulaires
Créer un formulaire
Dans le dossier AppBundle\Form, créer un fichier nommé ArticleType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('author', TextType::class)
->add('submit', SubmitType::class)
;
}
}
Gérer le formulaire
La validation
Le titre de mon article est obligatoire et ne doit pas dépasser 60 caractères
Le nom de l'auteur est aussi obligatoire et ne doit pas contenir de chiffre
Comment valide-t-on ces règles ?
=> Avec le validator !
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Article
{
// ...
/**
* @ORM\Column(type="string", length="60", nullable=false)
* @Assert\NotBlank()
* @Assert\Length(max=60, message="Le titre de l'article ne doit pas contenir plus de 60 caractères")
*/
private $title;
/**
* @ORM\Column(type="string", nullable=false)
* @Assert\NotBlank()
* @Assert\Regexp("[a-zA-Z]", message="Le nom de l'auteur ne doit pas contenir de caractères spéciaux")
*/
private $author;
// getters and setters
// ...
}
Dans le controller
<?php
namespace AppBundle\Controller;
class ArticleController extends Controller
{
/**
* @Route("/article/new", name="article_new")
*/
public function newAction(Request $request)
{
$form = $this->createForm(ArticleType::class, new Article());
if ($form->handleRequest($request)->isValid()) {
$article = $form->getData();
$manager = $this->getDoctrine()->getManagerForClass(Article::class);
$manager->persist($article);
$manager->flush();
return new RedirectResponse($this->generateUrl('article_show', [
'id' => $article->getId(),
]));
}
return $this->render(':article:new.html.twig', [
'form' => $form->createView(),
]);
}
}
Exercice
- Créer un nouveau Controller: ArticleController
- Ajouter 2 actions:
- newAction
- showAction
- Créer les templates associés aux actions
- Dans newAction, gérer le formulaire:
- s'il a été envoyé et valide, créer un nouvel article
- sinon, réafficher le formulaire
- Ajouter des contraintes, dans la classe Article
Les services
Un bout de code réutilisable partout dans l'application
Créer le service
services:
app.my_project.calculator:
class: 'AppBundle\Services\Calculator'
Dans app/config/services.yml
<?php
namespace AppBundle\Services;
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
}
Dans src/AppBundle/Services/Calculator.php
Utiliser le service
Dans une action de controller
<?php
// ...
public function helloAction($name)
{
// ...
$calculator = $this->container->get('app.my_project.calculator');
// ou, $this->get('...');
$result = $calculator->add(5, 5);
return $this->render(':default:hello.html.twig', [
// ...
'result' => $result
]);
}
Puis afficher le résultat dans la vue
Pleins de services dans Symfony
- mailer
- doctrine
- twig
- translator
- logger
- router
- session
- ...
La traduction
Activer la traduction
# app/config/config.yml
# décommenter la ligne
translator: { fallbacks: ["%locale%"] }
Utiliser le service de traduction
# En PHP
$this->get('translator')->trans('translation_key');
# En Twig
{{ 'tranlsation_key'|trans }}
Écrire la traduction
# app/Resources/translation/messages.fr.yml
translation_key: Le texte affiché en français
Le nom des fichiers de traduction doivent se nommer de cette manière: <domain>.<locale>.<loader>
ex: messages.fr.yml
Convention de nommage
Les domaines
- Le service translator va chercher les traductions correspondantes a des clés dans des "domaines"
- Un domaine est une manière de regrouper les traductions suivant le type
- Le domaine par défaut est: messages
- Il peut être changé en ce que l'on veut
Locale et loader
Locale: La langue dans laquelle on écrit la traduction
Loader: le type de format qu'on souhaite utiliser pour les traductions
Les différents loader supportés par Symfony sont:
- xml (xliff, format recommandé)
- yml
- php
Chacun des formats a ses avantages et inconvénients
Exercice
- Traduire les labels de formulaire
- Traduire les messages d'erreurs de validation
Bière ?
Symfony and Bananas v2
By keversc
Symfony and Bananas v2
- 1,165