From a741a6b20ea6f8c8b9b6692766173adf5398ff91 Mon Sep 17 00:00:00 2001 From: William Petit Date: Thu, 14 Dec 2017 17:32:17 +0100 Subject: [PATCH] =?UTF-8?q?Remise=20=C3=A0=20niveau=20Symfony3:=20slides?= =?UTF-8?q?=20+=20planning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- developpement/symfony3/presentation/slides.md | 705 ++++++++++++++++++ .../symfony3/ressources/planning.ods | Bin 0 -> 15871 bytes 2 files changed, 705 insertions(+) create mode 100644 developpement/symfony3/presentation/slides.md create mode 100644 developpement/symfony3/ressources/planning.ods diff --git a/developpement/symfony3/presentation/slides.md b/developpement/symfony3/presentation/slides.md new file mode 100644 index 0000000..11f6dc5 --- /dev/null +++ b/developpement/symfony3/presentation/slides.md @@ -0,0 +1,705 @@ + + +# Remise à niveau Symfony3 +## William Petit - S.C.O.P. Cadoles + +--- + + +# Les principales nouveautés de Symfony 3 + +- Simplification de l'authentification avec le système de "Guard" +- Composant LDAP pour l'authentification (compatible Active Directory) +- Amélioration des mécanismes d'injection de dépendances: "auto-wiring", services dépréciés... +- Un "MicroKernel" pour créer des applications minimalistes basées sur Symfony3 +- Et beaucoup d'autres améliorations et composants mineurs... + +--- + +# Structure d'un projet, générateurs et "bundles" + +## Amorçage d'un projet +## Notion de "bundle" +## Structuration d'un projet +## La console, les commandes et les générateurs + +--- + +## Amorçage d'un projet + +### Récupération de l'installeur Symfony + +```bash +sudo mkdir -p /usr/local/bin +sudo curl -LsS https://symfony.com/installer \ + -o /usr/local/bin/symfony +sudo chmod a+x /usr/local/bin/symfony +``` + +### Création du projet (avec la dernière LTS) + +```bash +symfony new 3.4 +``` +--- + +## Notion de "bundle" + +> Un "bundle" est un ensemble de fichiers (code, templates, etc) représentant une unité fonctionnelle dans le framework Symfony. + +**Avant Symfony 3** Les "bundles" étaient le moyen utilisé pour structurer le code d'une application. Une application pouvait donc avoir plusieurs bundles "internes", i.e. ne provenant pas d'un développeur tiers. + +**Après Symfony 3** Les bundles ne sont plus considérés à titre organisationnel mais uniquement comme un moyen de diffuser le code sous forme d'unité réutilisable. Une application ne devrait donc plus qu'avoir un seul bundle interne: `AppBundle`. + +--- + +## Structuration d'un projet (1) + +### Les principaux répertoires et leur rôle + +|Répertoire|Rôle| +|:-:|:-:| +|`app/config/`|Configuration de l'application| +|`app/Resources/`|Depuis Symfony3, répertoire contenant les vues ainsi les "assets" de l'application| +|`src/AppBundle/`|Sources de l'application| + + +--- +## Structuration d'un projet (2) + +|Répertoire|Rôle| +|:-:|:-:| +|`tests/`|Répertoire contenant les tests de l'application (unitaire comme fonctionnels)| +|`var/`|Répertoire contenant toutes les données "vivantes" de l'application| +|`vendor/`| Dépendances Composer +|`web/`| Répertoire des ressources publiques de l'application + +--- +## La console, les commandes et les générateurs (1) + +### Quelques commandes (très) utiles +|Commande|Description| +|:-:|:-:| +|`server:run`|Exécuter l'application avec le serveur HTTP PHP| +|`security:check`|Vérifier que les dépendances du projet ne comportent pas de vulnérabilités connues| +|`debug:container`|Retrouver le mapping services <-> classe PHP| +|`router:match`|Vérifier quelle controleur/action sera utilisée pour un chemin donné| +--- + +## La console, les commandes et les générateurs (2) + +### Les générateurs par défaut +|Commande|Description| +|:-:|:-:| +|`generate:bundle`|Créer un nouveau bundle| +|`generate:controller`|Créer un nouveau contrôleur| +|`generate:command`|Créer une nouvelle commande| +|`doctrine:generate:entity`|Créer une nouvelle entité Doctrine| +--- + +# Le routage et les contrôleurs + +## Création d'un nouveau contrôleur +## Déclaration des contrôleurs +## Actions, routes et verbes HTTP avec les annotations + +--- + +## Création d'un nouveau contrôleur + +```bash +bin/console generate:controller +``` +--- + +## Déclaration des contrôleurs + + +--- + +## Actions, routes et verbes HTTP avec les annotations (1) + +### Action annotée basique +```php +class DemoController extends Controller +{ + /** + * @Route("/demo") + */ + public function demoAction() + { + // ... + } + +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (2) + +### Action avec méthodes HTTP contraintes + +```php +class DemoController extends Controller +{ + /** + * @Route( + * "/demo", + * methods = { "POST", "PUT" } + * ) + */ + public function demoAction() + { + // ... + } +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (3) + +### Action nommée + +```php +class DemoController extends Controller +{ + /** + * @Route("/demo-named", name="my_demo_action") + */ + public function demoAction() + { + // ... + } +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (4) + +### Action avec paramètres + +```php +class DemoController extends Controller +{ + /** + * @Route("/demo/{myParam}") + */ + public function demoAction($myParam) + { + // ... + } + + /** + * @Route("/demo/{paramWithDefaultVal}") + */ + public function demoAction($paramWithDefaultVal = 1) + { + // ... + } +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (5) + +### Action avec paramètres contraints + +```php +class DemoController extends Controller +{ + /** + * @Route( + * "/demo/{myParam}", + * requirements = { "myParam"="^\d+$" } + * ) + */ + public function demoAction($myParam) + { + // ... + } +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (6) + +### Action avec paramètres contraints + +```php +class DemoController extends Controller +{ + /** + * @Route( + * "/demo/{myParam}", + * requirements = { "myParam"="^\d+$" } + * ) + */ + public function demoAction($myParam) + { + // ... + } +} +``` +--- + +## Actions, routes et verbes HTTP avec les annotations (7) + +### Paramètres spéciaux + +|Paramètre|Description| +|:-:|:-:| +|`_locale`| La "locale" utilisé par l'application pour la requête en cours | +|`_format`| Le "format" utilisé par l'application pour la requête en cours | +|`_controller`| L'identifiant du contrôleur et son action utilisés pour traiter la requête en cours| + +--- + +## Actions, routes et verbes HTTP avec les annotations (8) + +### Générer une URL pour une route nommée + +```php +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +$this->generateUrl( + 'demo', // Nom de la route + ['myParam' => 'my-value'], // Paramètres à injecter + // L'URL générée doit elle être absolue ou non ? + UrlGeneratorInterface::ABSOLUTE_URL +); +``` +--- + +# Services + +## Utilisation des services +## Création de nouveaux services + +--- + +## Utilisation des services (1) + +### Lister les services existants + +```bash +bin/console debug:container +``` + +--- + +## Utilisation des services (2) + +### Utiliser un service (méthode classique) + +```php +class DemoController extends Controller +{ + /** + * @Route("/demo") + */ + public function demoAction() + { + $logger = $this->get('logger'); + $logger->info('Hello world !'); + } +} +``` +--- + +## Utilisation des services (3) + +### Utiliser un service (Via le "type hint") + +```php +use Psr\Log\LoggerInterface; + +class DemoController extends Controller +{ + /** + * @Route("/demo") + */ + public function demoAction(LoggerInterface $logger) + { + $logger->info('Hello world !'); + } +} +``` + +**Tip** Pour identifier les différentes interfaces disponibles, utilisez la commande `bin/console debug:autowiring` + +--- + +## Création d'un service (1) + +### Création de la classe PHP + +```php +namespace AppBundle\Service; + +class MyService { + public function doNothing() {} +} +``` + +**Tip** On peut vérifier que le service est bien détecté par l'application avec la commande `bin/console debug:autowiring` + +--- + +## Création d'un service (2) + +### Utilisation du service dans un contrôleur + +```php +use AppBundle\Service\MyService; + +class DemoController extends Controller +{ + /** + * @Route("/demo") + */ + public function demoAction(MyService $my) + { + $my->doNothing(); + } +} +``` + +--- + +## Création d'un service (3) + +### Déclaration de dépendances inter-services + +```php +namespace AppBundle\Service; + +use Psr\Log\LoggerInterface; + +class MyService { + + private $logger; + + public function __construct(LoggerInterface $logger) { + $this->logger = $logger; + } + + public function log($message) { + $this->logger->info($message); + } + +} +``` + +--- + +## Création d'un service (4) + +### Déclaration de dépendances vers des valeurs de configuration + +```php +namespace AppBundle\Service; + +class MyService { + + private $myCustomParameter; + + public function __construct($myCustomParameter) { + $this->myCustomParameter = $myCustomParameter; + } + +} +``` +--- + +## Création d'un service (5) + +### Déclaration explicite de la dépendance + +Dans `app/config/services.yml`, déclarer la dépendance explicitement: +```yaml +services: + AppBundle\Service\MyService: + arguments: + $myCustomParameter: 'test' +``` + +--- + +# Authentification et autorisation + +## Le fichier `app/config/security.yml` +## Firewalls, providers et encoders +## Gestion des rôles et contrôle des accès +## Méthode d'authentification personnalisée + +--- + +## Le fichier `app/config/security.yml` + +### Authentifications et autorisations + +--- + +## Firewalls (1) + +### Déclarer une nouvelle méthode d'authentification dans un firewall + +```yaml +security: + firewalls: + main: + anonymous: ~ + http_basic: ~ +``` + +--- + +## Firewalls (2) + +### Multiple firewalls + +```yaml +security: + + firewalls: + + authenticated_area: + pattern: ^/secured + http_basic: ~ + + public_area: + anonymous: ~ + +``` +--- + +## Providers (1) + +### Utilisation du provider `memory` + +```yaml +security: + + providers: + in_memory: + memory: + users: + bob: + password: bob + roles: 'ROLE_USER' + admin: + password: 123456 + roles: 'ROLE_ADMIN' + +``` + +--- + +## Providers (2) + +### Déclaration de l'`encoder` pour la gestion des mots de passe + +```yaml +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext +``` + +--- + +## Providers (3) + +### Utilisation d'un `encoder` apportant un meilleur niveau de sécurité + +```yaml +security: + encoders: + Symfony\Component\Security\Core\User\User: + algorithm: bcrypt + cost: 12 +``` +#### Calcul de l'empreinte d'un mot de passe + +```bash +bin/console security:encode-password +``` + +--- + +## Gestion des rôles et contrôle des accès (1) + +## Définition de rôles + +```yaml +security: + access_control: + - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY } +``` + +--- + +## Gestion des rôles et contrôle des accès (2) + +## Restriction d'accès par adresse IP + +```yaml +security: + access_control: + - path: ^/local-api + roles: ROLE_API_USER + ips: [127.0.0.1] +``` + +--- + + +## Gestion des rôles et contrôle des accès (3) + +## Définition de règles d'accès pour les actions + +```php +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; + +class DemoController extends Controller +{ + /** + * @Route("/demo") + * @Security("has_role('ROLE_ADMIN')") + */ + public function demoAction() + { + // ... + } + +} +``` +--- + +## Méthode d'authentification personnalisée + +### Exercice + +Implémenter une classe `Guard` pour créer un firewall pour un serveur d'authentification "passwordless" basé sur JWT. + +**Ressources** +- Tutoriel Symfony3 sur l'utilisation de la classe Guard https://symfony.com/doc/3.4/security/guard_authentication.html +- Instance de démonstration du serveur d'authentification: https://forge.cadoles.com/wpetit/ciku +- Sources du serveur d'authentification: https://forge.cadoles.com/wpetit/ciku + +--- + +# Les vues et le moteur de templating Twig + +## Organisation des vues +## Syntaxe Twig +## Héritage et composition +## Étendre Twig + +--- + +## Organisation des vues dans le projet + +--- + +## Syntaxe Twig + +```twig + + +

{{ myVar }}

+ + + +``` + +--- + +## Héritage et composition (1) + +### Héritage + +```twig +// parent.html.twig + + + {% block body %} +
+ Default content +
+ {% endblock %} + + +``` + +```twig +// child.html.twig +{% extends 'parent.html.twig' %} +{% block body %} +
+ Child content +
+{% endblock %} +``` +--- + +## Héritage et composition (21) + +### Composition + +```twig +// layout.html.twig + + + {% for i in items %} + {{ include('_partial.html.twig', { 'item': i }) }} + {% endfor %} + + +``` +```twig +// _partial.html.twig +
+ Item #{{ item.id }} +
+``` +--- + +## Étendre Twig + + + +--- + +# Les formulaires + +## Création et traitement de formulaires +## Validation des données +## Les évènements + +--- + +# L'ORM Doctrine et le modèle de données + +## Concept d'ORM +## Entité et Dépôt +## Les évènements + +--- + +# Mise en production + +## Gestion des environnements +## Cache applicatif + +--- + +# Licence + +## CC BY-NC-SA 3.0 FR + +[Creative Commons - Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France](https://creativecommons.org/licenses/by-nc-sa/3.0/fr/) diff --git a/developpement/symfony3/ressources/planning.ods b/developpement/symfony3/ressources/planning.ods new file mode 100644 index 0000000000000000000000000000000000000000..b36809ed463229d2cb503b56ea6148655dfd660d GIT binary patch literal 15871 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 literal 0 HcmV?d00001