Sous Windows, pour savoir quel processus utilise un port, lancez la commande Get-Process
dans Powershell :
Exemple pour le port 5433
:
Get-Process -Id (Get-NetTCPConnection -LocalPort 5433).OwningProcess
Publié le :
Sous Windows, pour savoir quel processus utilise un port, lancez la commande Get-Process
dans Powershell :
Exemple pour le port 5433
:
Get-Process -Id (Get-NetTCPConnection -LocalPort 5433).OwningProcess
Publié le :
Voici un modèle de composant React minimal :
// MyComponent.tsx
import React, { ReactNode } from 'react';
import styles from "./MyComponent.module.scss";
interface Props {
children: ReactNode;
notRequiredProperty?: string;
}
export const MyComponent: React.FC = ({ children, notRequiredProperty }: Props) => {
return <div className={styles.myComponentClass}>{children}< /div>;
};
Explications :
<MyComponent children={<p>EXAMPLE</p>} />
MyComponent.module.scss
, dans lequel se trouve une règle CSS pour la
classe myComponentClass
.children
.Props
pour définir les propriétés du composant. Cela facilite l’autocomplétion et
permet la validation du typage à la compilation.notRequiredProperty
est facultative lorsqu’on appelle le composant.Si on souhaite créer un wrapper pour un composant existant, on veut souvent qu’il accepte les mêmes propriétés. Dans ce cas, on peut déclarer l’interface et le composant ainsi :
type Props = SomeComponentToWrapProps & {
extraProperty1: string
};
export const MyComponent: React.FC = ({ extraProperty1, inheritedProp1, ...others }: Props) => {
// [...]
};
Explications :
SomeComponentToWrapProps
, avec celle déclarée ici.others
.Publié le :
Contrairement à Angular, React propose peu de solutions par défaut aux problèmes les plus courants. Il faut souvent ajouter des modules en dépendance, et il est parfois difficile de ne pas se perdre dans la jungle des modules pour React.
Voici une liste de modules et de pratiques utiles pour les besoins courants.
On peut initialiser facilement un projet React avec l’outil CRA
(Create React App).
Il crée l’arborescence de base, via une simple commande comme npx create-react-app my-app --template typescript
.
Le module react-router fait ça très bien.
Pour récupérer des données auprès d’une API ou via d’autres méthodes asynchrones, on peut utiliser SWR ou react-query.
Si on souhaite gérer un état globale de l’application au niveau local, on peut utiliser redux avec redux-toolkit ou alors recoil.
Le module react-hook-form fait ça très bien. Il supporte notamment Yup pour gérer la validation de champs.
Le module react-i18next permet de gérer une appli multilingue.
Pour restreindre des classes CSS à un composant en particulier, on peut utiliser le concept de modules CSS.
Les principales bibliothèques graphiques sont disponibles pour React, et notamment Material UI. Elle fonctionne très bien, sauf avec le hook-form. C’est problématique et cela nécessite de recréer un composant « Contrôlable », pour chaque type de champ de formulaire.
Plusieurs bibliothèques pour les tests fonctionnent bien avec React, notamment jest.
On peut combiner prettier et eslint, pour normaliser la base de code. Il existe des extensions à eslint spécialement pour React, afin de détecter et remonter si certaines mauvaises pratiques sont utilisées.
Pour documenter et présenter une bibliothèque de composants, on peut utiliser Storybook. Cela permet de manipuler et tester chaque composant, dans une interface dédiée.
Pour utiliser plus facilement la base de données locale IndexedDB, on peut y accéder via dexie.
Pour extraire les données d’un jeton JWT, on peut utiliser jwt-decode.
Publié le :
Régulièrement en Javascript, on veut écouter les changements sur un élément, mais ne les prendre en compte qu’à
intervalle régulier.
Par exemple, si on écoute le déplacement de la souris pour lancer un traitement, on n’a pas besoin de l’exécuter
à chaque changement de position, mais uniquement tous les 300 ou 500ms.
Pour éviter l’effet « rebond », on utilise alors une fonction de debounce, ou dans React, un hook.
import { useEffect, useState } from 'react';
/**
* Delays the value change, so it’s only committed once during the specified duration.
*
* Usage :
* <pre>
* const debouncedValue = useDebouncedValue(realTimeValue, 500);
* <pre>
*/
export const useDebouncedValue = <T>(input: T, duration = 500): T => {
const [debouncedValue, setDebouncedValue] = useState(input);
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(input);
}, duration);
return () => {
clearTimeout(timeout);
};
}, [input, duration]);
return debouncedValue;
};
On veut afficher un message dans la console au changement de valeur du champ de formulaire,
mais limiter ce traitement avec un debounce.
import React, { useEffect, useState } from 'react';
import { useDebouncedValue } from './utils/debounce';
export default (() => {
const [myValue, setMyValue] = useState('');
const myDebouncedValue = useDebouncedValue(myValue, 500);
useEffect(() => {
console.log('This log is displayed when the value of debouncedFilterNom changes', myDebouncedValue);
}, [myDebouncedValue]);
const handleValueChange = async (event: any) => {
setMyValue(event.target.value);
};
return (
<form>
<input type="text" onChange={handleValueChange} value={myValue} />
</form>
);
}) as React.FC;
Publié le :
Voici une fonction pour construire une queryString à partir d’un objet.
Version JavaScript :
export const buildQueryString = (parameters) => {
const paramParts = Object.entries(parameters)
.map(([key, value]) => `${key}=${encodeURI(value)}`);
return paramParts.length > 0 ? `?${paramParts.join('&')}` : '';
};
Version TypeScript :
export const buildQueryString = (parameters: { [key: string]: string }) => {
const paramParts = Object.entries(parameters)
.map(([key, value]) => `${key}=${encodeURI(value)}`);
return paramParts.length > 0 ? `?${paramParts.join('&')}` : '';
};
Explications :
&
et ?
est ajouté en préfixe de la chaîne.Publié le :
Il est possible de modifier l’auteur de tous les anciens commits d’un dépôt Git.
Pour cela, utilisez la commande suivante, après l’avoir personnalisée selon vos besoins :
git filter-branch --env-filter '
WRONG_EMAIL="wrong@email.com"
NEW_NAME="John Doe"
NEW_EMAIL="j.doe@email.com"
if [ "$GIT_COMMITTER_EMAIL" = "$WRONG_EMAIL" ]
then
export GIT_COMMITTER_NAME="$NEW_NAME"
export GIT_COMMITTER_EMAIL="$NEW_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$WRONG_EMAIL" ]
then
export GIT_AUTHOR_NAME="$NEW_NAME"
export GIT_AUTHOR_EMAIL="$NEW_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags
Explications :
On indique l’adresse email utilisée par tous les commits que l’on souhaite modifier, ainsi que le nom d’utilisateur et l’adresse email correcte que l’on souhaite utiliser à la place.
Publié le :
Sous Linux, l’utilitaire ncdu (pour NCurses Disk Usage) permet de trouver quel fichier ou répertoire vous bouffe tout votre espace disque.
Il est disponible sur les dépôts officiels Debian et Ubuntu et donc facile à installer.
sudo apt install ncdu
ncdu
Note : ncdu a été réécrit dans une v2 encore récente. Selon la version des dépôts, c’est peut-être la v1 qui sera installée.
Publié le :
Dans une application ou un site web, on peut stocker des informations dans le navigateur de l'utilisateur, pour un nom de domaine spécifique.
Deux approches principales : les cookies et le Storage
. Pour la seconde, deux possibilités :
sessionStorage
ou localStorage
.
La principale différence, c'est que les cookies sont accessibles côté client et côté serveur.
À l'inverse, il n'y a aucun moyen côté serveur d'accéder au contenu ni du localStorage
ni du sessionStorage
.
Remarque :
Quand on parle de "sessions" pour le sessionStorage
, il n'y a aucun rapport avec les sessions
entre client/serveur comme on peut les manipuler en PHP, par exemple.
Les données stockées en sessionStorage sont rattachées à un onglet.
Si on recharge la page au sein d'un même onglet, les données sont conservées.
En revanche, elles expirent à sa fermeture.
Les données stockées en sessionStorage sont rattachées à un onglet.
Remarque :
Selon le navigateur, si on restaure un onglet fermé précédemment, on récupère le sessionStorage
tel qu'il était avant fermeture. C'est le comportement pour les navigateurs basés sur Chrome.
De même, si on duplique un onglet ouvert, la copie hérite d'une copie du sessionStorage
de l'onglet original.
Publié le :
Pour faire du refactoring automatique en PHP, on peut utiliser son IDE ou la lib Rector (installable via Composer).
Elle permet notamment d'automatiser la migration de code PHP vers une version plus récente de PHP.
Quelques exemples :
??
et ?:
Il est également possible de créer ses propres règles de refactoring.
Grafikart a sorti une vidéo de présentation de l'outil.
Publié le :
Si vous construisez des APK Android avec Cordova, vous pouvez rencontrer ce genre d'erreur :
[...]
> Task :app:preReleaseBuild FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Could not resolve all files for configuration ':app:releaseRuntimeClasspath'.
> Could not resolve com.android.support:support-v4:27.+.
Required by:
project :app
> Failed to list versions for com.android.support:support-v4.
> Unable to load Maven meta-data from https://jcenter.bintray.com/com/android/support/support-v4/maven-metadata.xml.
> Could not get resource 'https://jcenter.bintray.com/com/android/support/support-v4/maven-metadata.xml'.
> Could not GET 'https://jcenter.bintray.com/com/android/support/support-v4/maven-metadata.xml'.
> Read timed out
[...]
Cela est dû à la coupure du dépôt jcenter.
La solution pour le remplacer est décrite dans plein de tickets sur StakOverflow (ex: Question #67418153).
En bref : il faut modifier le fichier build.gradle
.
Malheureusement, vous n'avez peut-être pas accès à ce fichier, si c'est une Plateforme d'Intégration Continue qui se charge du build.
Dans ce cas, ce fichier n'est probablement pas versionné, mais générée automatiquement dans un répertoire nommé platforms/
.
Entre chaque build, veillez à bien supprimer ce répertoire platforms/
.
Ainsi, le fichier build.gradle
sera régénéré en utilisant des dépôts corrects.
Publié le :
bin/console
bin/console debug:autowiring
Cela liste les interfaces disponibles.
Pour en rechercher une concernant un sujet (ex: cache, mail, markdown)
bin/console debug:autowiring markdown
Pour voir toutes les implémentations de ces interfaces
bin/console debug:container
Ou pour avoir le détail sur l'une d'entre elles (ex: markdown)
bin/console debug:container markdown
Pourvoir les paramètres présents dans le conteneur
bin/console debug:container markdown
bin/console debug:container --parameters
Cela liste toutes la configuration par défaut pour le bundle.
Pour voir la config actuelle
bin/console debug:config MyBundle
bin/console debug:router --show-controllers
Affiche la liste de toutes les routes et les contrôleurs/actions associés :
La commande make
permet de générer un squelette de classe
(Command, TwigExtension, ...) :
bin/console make
dump($someVariable);
Pour afficher une variable et stopper l'exécution :
// "Dump and Die"
dd($someVariable);
Pour afficher toute la syntaxe ainsi que les variables globales disponibles dans Twig :
bin/console debug:twig
La section _default
est héritée par toutes les déclarations suivantes.
C'est pourquoi si on y utilise bind
pour binder des arguments, ils seront
disponibles pour tous les services.
On peut typer ces arguments. Ex:
services:
_default:
bind:
bool $isDebug: '%kernel.debug%'
Psr\Log\LoggerInterface $someLogger: '@monolog.logger.some_logger'
Quand il y a plusieurs implémentations d'une interface
(par exemple LoggerInterface, cf.bin/console debug:autowiring log
), on peut
accéder à celui que l'on veut en nommant l'argument comme indiqué.
Ex:
public function __constructor(LoggerInterface $consoleLogger) {}
// à la la place de
public function __constructor(LoggerInterface $logger) {}
Un alias de service peut être créé en une ligne :
services:
Some\Path\To\SomeService $newName: '@id_of_the_target_service'
Monolog peut loguer des messages sur des channel spécifiques. Pour cela il faut :
# ex: dans monolog.yaml
monolog:
channels: ['my_new_channel']
bin/console debug:autowiring log
)public function __constructor(LoggerInterface $myNewChannelLogger) {}
Ajoute des extensions doctrine (Slug, Blameable, Softdeleteable, ...)
Ajoute le filter twig ago
, pour afficher depuis quand la date est passée.
Ajoute une commande Symfony (make:factory
) pour générer des Factory pour les entités.
Compatible avec le bundle ci-dessous
Permet de générer du faux contenu de manière intelligente.
Publié le :
Cet article est une synthèse de la documentation officielle : Forms.
Dans Symfony, un formulaire et un champ sont représentés tous deux par la FormInterface
.
Un formulaire contient des champs ou des sous-formulaires enfants, pouvant eux-mêmes en avoir.
(Il s'agit là du Design Pattern Composite.)
La FormTypeInterface
permet de représenter un type de champ/formulaire (ex: ContactFormType
,
BirthdayType
, ...). En fonction de ce type, le champ/formulaire aura telles ou telles options, et tel ou tel rendu.
La classe BaseType
l'implémente. En héritent deux sous-classes : FormType
pour les champs et ButtonType
pour
les boutons.
Pour définir un nouveau type de champ/formulaire, il faut donc implémenter FormTypeInterface
.
Pour simplifier cette tâche, on étend AbstractType
, qui implémente déjà les méthodes avec des comportements neutres.
De plus, on surcharge éventuellement sa méthode getParent()
, pour indiquer le type précis dont on veut hériter les
propriétés (ex: EmailType
, EntityType
, ...).
Cette mécanique consistant à ne pas utiliser l'héritage direct PHP des FormTypes qui nous intéresse, évite de
devoir appeler nous-même les méthodes parentes avant d'y ajouter nos traitements spécifiques.
L'appel au parent est fait automatiquement et nous n'avons qu'à nous occuper des traitements spécifiques.
Exemples natifs :
use \Symfony\Component\Form\AbstractType;
// Héritage PHP du type neutre
class DateType extends AbstractType {
public function getParent() {
// Héritage des propriétés/options/... du type de base
return FormType::class;
}
}
// Héritage PHP du type neutre
class BirthDayType extends AbstractType {
public function getParent() {
// Héritage des propriétés/options/... de DateType
return DateType::class;
}
}
// Héritage PHP du type neutre
class FormType extends AbstractType {
public function getParent() {
// Pas d'héritage de propriétés/options/...
return null;
}
}
Pour des modifications légères de types déjà existants (par exemple ajouter des options), on peut implementer
l'interface FormTypeExtensionInterface
.
Pour créer un formulaire, on utilise un FormBuilder
ou on laisse Symfony deviner les champs en fonction d'une classe.
Pour cela :
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
// Depuis un contrôleur étendant AbstractController
$form = $this->createFormBuilder($task)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, ['label' => 'Create Task'])
->getForm();
// ou en laissant Symfony deviner
$form = $this->createForm(Task::class, $task, $options);
// Depuis n'importe où
/** @var \Symfony\Component\Form\FormFactoryInterface $form */
$form = $this->formFactory
-> createBuilder(Task::class, $task, $options)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, ['label' => 'Create Task'])
->getForm();
// ou en laissant Symfony deviner
$form = $this->formFactory->create(Task::class, $task, $options);
Dans le second cas, Symfony se base sur des Guesser qui devinent les champs à partir :
FormInterface
définit la méthode handleRequest()
, chargée de vérifier que tout est valide puis éventuellement
d'appeler la méthode submit()
du formulaire :
À la soumission du formulaire (ak. méthode submit()
) :
addError()
est appelée sur le champLa commande suivante permet de lister toutes les options d'un type de champ/formulaire et d'indiquer si elles sont héritées ou surchargées :
# Remplacer EntityType par n'importe quel FormType
bin/console debug:form EntityType
Il est considéré comme une bonne pratique, de rendre directement un formulaire ou un de ses champs via les fonctions de rendu dédiées. Toute modification du balisage qu'elles génèrent devrait être réalisée via la surcharge de thème.
Par défaut, plusieurs thèmes sont disponibles, notamment Bootstrap 3 et 4. Pour en ajouter :
# config/packages/twig.yaml
twig:
form_themes:
- 'form/my_custom_form_theme.html.twig'
Un formulaire ou un champ dans un template est un objet de type FormView
.
Il est possible de choisir un thème spécifique pour rendre un formulaire ou un champ, via :
{% form_theme my_form 'form/app_custom_form_theme.html.twig' %}
{% form_theme my_form.my_field 'form/app_custom_form_theme.html.twig' %}
Chaque thème définit un certain nombre de blocs, nommés d'une manière à définir quel type de champ il est susceptible
de rendre (ex: integer_widget
).
Pour le surcharger, il suffit de redéfinir le bloc dans le template courant, comme n'importe quel bloc.
Les suffixes disponibles pour les noms de bloc sont les suivants : _widget
, _row
, _label
, _help
.
Les préfixes disponibles sont listés dans la variable block_prefixes
présente dans le contexte du bloc.
Exemple : ['form','text','textarea', '_post_content']
, pour un champ nommé content
, de type TextAreaType
(héritant de TextType
héritant lui-même de FormType
), au sein du type de formulaire spécifique PostType
.
Un data transformer est chargé de transformer une donnée (sous forme de tableau, objet, entier, ...) vers une chaîne (ou un tableau de chaînes) exploitable dans un formulaire, et vice versa.
Il est représenté par l'interface DataTransformerInterface
et requiert deux méthodes :
transform()
: Donnée => (tableau de) chaîne(s) de caractèresreverseTransform()
: (Tableau de) chaîne(s) de caractères => donnéePour en ajouter un à formulaire, on utilise le FormBuilder
:
$formBuilder->addViewTransformer(new MyCustomTransformer());
// Note: il existe également cette méthode si la transformation doit être effectuée en amont de la transformation
// de la précédente
$formBuilder->addModelTransformer(new MyCustomModelTransformer());
Tous les évènements liés aux formulaires sont des FormEvent
, permettant d'accéder aux données du formulaire et
éventuellement de les modifier.
Ils ont lieu dans cet ordre chronologique :
FormEvents::PRE_SET_DATA
: on peut y modifier la donnée en fonction du formulaire
Hydratation du formulaire et ses enfants, avec la donnée
FormEvents::POST_SET_DATA
: on peut y modifier le formulaire en fonction de la donnée (ex: champ affiché en fonction
d'un autre champ)
FormEvents::PRE_SUBMIT
: on peut y changer la donnée soumise dans la requête (ex: la normaliser, la trimmer, ...)
Transformation vers un "objet" de données, via les méthodes reverseTransform()
des DataTransformer
.
FormEvents::SUBMIT
: les données sont prêtes, mais peut-être pas celles des parents
FormEvents::POST_SUBMIT
: les données sont prêtes, le composant Validation écoute cet évènement pour les valider
90% du temps, on n'a besoin d'agir que sur les évènements FormEvents::POST_SET_DATA
ou FormEvents::PRE_SUBMIT
uniquement.
Publié le :
Cet article est une synthèse de la documentation officielle : Create your First Page in Symfony.
Créer une page, c'est indiquer quelle réponse générer lorsqu'on reçoit une requête auprès d'une certaine URL.
Plus, précisément, la partie de l'URL qui nous intéresse est appellée la route, ou en français, le chemin.
Par exemple pour les URL http://mon-site.com/recherche
et http://mon-site.com/article/1234-mon-article
,
les routes seront respectivement /recherche
et /article/1234-mon-article
.
Le générateur de réponse est une fonction PHP appelée contrôleur. Dans Symfony, elle doit retourner un
objet Response
, contenant du texte (HTML, JSON, ...), une fichier binaire (ex: fichier, image, ...), ...
On appelle route le chemin présent dans l'URL permettant de déterminer qu'elle génération on souhaite.
Par défaut, les routes se déclarent dans le fichier config/routes.yaml
. Par exemple :
# the "app_lucky_number" route name is not important yet
my_route_name:
path: /some-path-to/my_page
controller: App\Controller\MyPageController::myFunctionToGenerateThePage
On a ici définit que les requêtes sur la route /some-path-to/my_page
devait recevoir une réponse
générée par la méthode myFunctionToGenerateThePage()
de la classe App\Controller\MyPageController
.
Généralement, les contrôleurs seront des méthodes de classes placées dans le répertoire src/Controller
.
Pour faciliter le mapping route-controller, vous pouvez déclarer vos routes en annotations à la place d'utiliser le fichier
routes.yaml
. Cela regroupe ainsi la route et le contrôleur au même endroit.
Pour cela, la dépendance annotations
doit être installée :
coomposer require annotations
Vous pouvez dès lors indiquer la route en annotation de votre contrôleur :
<?php
// src/Controller/MyPageController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MyPageController
{
/**
* @Route("/some-path-to/my_page", name="my_route_name")
*/
public function myFunctionToGenerateThePage(): Response
{
return new Response(
'<html><body>Hey guys!</body></html>'
);
}
}
Remarque : Si vous utilisez PHP 8, vous pouvez utiliser directement les annotations du langage :
#[Route('/some-path-to/my_page', name: 'my_route_name')]
Pour lister les routes disponible et les contrôleurs associés, utilisez la commande suivante :
bin/console debug:router
Pour faciliter l'écriture de pages HTML, Symfony propose le moteur de templates Twig.
Il faut installer la dépendance :
coomposer require twig
Vous pouvez désormais utiliser ce moteur dans vos contrôleurs. Pour y avoir accès, la solution
la plus simple est de faire étendre vos contrôleurs de la classe AbstractController
. Ex :
// src/Controller/MyPageController.php
// ...
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- class MyPageController
+ class MyPageController extends AbstractController
{
// ...
}
La méthode render()
vous permet de transformer un template twig en du code HTML et de
le placer dans un objet Response
:
<?php
// src/Controller/MyPageController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MyPageController extends AbstractController
{
/**
* @Route("/some-path-to/my_page", name="my_route_name")
*/
public function myFunctionToGenerateThePage(): Response
{
return $this->render('some-path-to/my-page.html.twig', [
'who' => 'guys',
]);
}
}
{# templates/some-path-to/my-page.html.twig #}
<html>
<body>
<h1>Hey {{ who }}!</h1>
</body>
</html>
Remarques :
Ici, on a définit la variable who
avec pour valeur guys
et on l'a transmise au template
en argument de la fonction render()
. La syntaxe {{ my_var }}
permet de l'afficher.
En général, on n'ajoute pas les balises <html>
et <body>
directement dans le template.
À la place, on utilise l'héritage de Twig et notre template étend alors base.html.twig
, par exemple.
Voici les principaux répertoires de votre projet Symfony :
├── bin/
│ └── console # exécutable pour lancer les commandes Symfony
├── config/ # répertoire contenant les fichiers de config des routes, services et dépendances
├── public/ # répertoire contenant tous les fichiers accessibles publiquement
│ ├── a-public-file.txt
│ └── index.php
├── src/ # répertoire contenant tout votre code PHP
├── templates/
├── var/ # répertoire contenant les fichiers auto-générés
│ ├── cache/
│ └── log/
├── vendor/ # répertoire contenant les dépendances installées avec Composer (dont Symfony !)
Publié le :
Cet article est une synthèse de la documentation officielle : Routing.
Qu'est-ce qu'une route ?
C'est une configuration qui décrit comment trouver le contrôleur à exécuter pour un chemin (ou un ensemble de chemins) donné.
Pour cela, elle va décrire précisément :
Elle peut être de différents types, Symfony gérant nativement annotation, yaml, xml et php.
Est-il possible d'avoir une route par langue pour un même contrôleur ? (ex: /about-us
et /a-propos-de-nous
)
Oui, en indiquant un tableau associatif pour path :
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route({
* "en": "/about-us",
* "nl": "/over-ons"
* }, name="about_us")
*/
public function about(): Response
{
// ...
}
Pour indiquer une regex décrivant le format attendu pour l'un des paramètres de la route, deux syntaxes sont possibles :
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
*/
public function list(int $page): Response
{
// ...
}
/**
* @Route("/blog/{slug<[a-z-]+>}", name="blog_show")
*/
public function show(string $slug): Response
{
// ...
}
Explications :
requirements
pour définir la regex du paramètre page
.Même chose, deux possibilités :
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}, defaults={"page"="homepage"})
*/
public function list(int $page): Response
{
// ...
}
/**
* @Route("/blog/{slug<[a-z-]+>?homepage}", name="blog_show")
*/
public function show(string $slug): Response
{
// ...
}
Notes :
"/blog/{slug?homepage}"
).?
, alors le paramètre devient optionnelLa génération est fournie via la méthode generate()
du service
UrlGeneratorInterface
.
Elle peut générer plusieurs choses selon le paramètre $referenceType
qu'on lui fournit :
UrlGeneratorInterface::ABSOLUTE_URL
: une URL absolue (ex: http://example.com/dir/file
)UrlGeneratorInterface::ABSOLUTE_PATH
: un chemin absolu (ex: /dir/file
)UrlGeneratorInterface::RELATIVE_PATH
: un chemin relatif au chemin de la requête courante (ex: ../parent-file
)UrlGeneratorInterface::NETWORK_PATH
: un chemin réseau (ex: //example.com/dir/file
)Le RedirectController
fourni par Symfony, permet des redirections temporaires ou permanente
Il s'utilise directement par configuration dans le routing.yaml
.
L'ArgumentResolver
rend disponible automatiquement les attributs de la requête par injection dans
l'action de contrôleur.
La variable injectée doit avoir le nom de l'attribut (commençant par un _
). Ex: $_route_params
, $_controller
, ...
Note : On peut également les récupérer classiquement via la Request dans le bag attribute
.
Il est possible de définir des conditions spécifiques de match pour une route.
Celles-ci sont définies par de l'expression language.
Ex :
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
/**
* @Route(
* "/contact",
* name="contact",
* condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
* )
*
* expressions can also include config parameters:
* condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
*/
public function contact(): Response
{
// ...
}
}
Symfony fournit 2 commandes utiles pour travailler avec le routage :
bin/console debug:router --show-controllers
: pour lister toutes les routes gérées et par quels contrôleursbin/console debug:router name_of_the_route
: pour afficher la configuration complète d'une routebin/console router:match /some/url [--method=GET]
: pour afficher si l'URL match avec une route et si oui laquellePour pouvoir charger des routes provenant d'une source spécifique (ex: base de donnée, fichier .ini, ...), on peut créer un Route Loader spécifique.
Celui-ci doit étendre Symfony\Component\Config\Loader\Loader\Loader
.
En surchargeant la méthode supports()
, il peut gérer des types de route spécifiques.
Publié le :
Un composant est une bibliothèque écrite en PHP, pouvant fonctionner sans Symfony.
Exemples : Mailer, HttpClient, Form, ...
Un bundle est un plugin pour Symfony, servant d'interface entre une bibliothèque et Symfony.
Il permet par exemple de configurer facilement la bibliothèque via Symfony.
Exemples : twig-bundle
qui permet de configurer Twig
Un bridge est un élément Symfony permettant d'étendre une bibliothèque en y ajoutant
des choses utiles à Symfony
Exemples : twig-bridge
qui ajoute des fonctions à twig
La liste des best practices est disponible ici.
├── assets/
├── bin/
│ └── console
├── config/
├── public/
│ └── index.php
├── src/
│ └── Kernel.php
├── templates/
├── tests/
├── translations/
├── var/
│ ├── cache/
│ └── log/
├── vendor/
Non recommandé :
config/
: peut poser problème pour le déploiement des recipes FlexConfigurable :
/var/cache/
et /var/log/
: via surcharge des méthodes getCacheDir()
et getCacheLog()
du Kernel.php
templates/
: via twig.default_path
(par défaut dans le twig.yaml
)translations/
: via framework.translator.default_path
(par défaut dans le translation.yaml
)Risqué à modifier :
public/
: vérifier que tous les chemins restent corrects dans l'index.php
.
Il faut également ajouter la propriété extra.public-dir
dans le composer.json
(si on utilise Flex)
vendor/
: vérifier que tous les chemins restent corrects dans l'index.php
.
Il faut également ajouter la propriété config.vendor-dir
dans le composer.json
.
C'est un plugin Composer qui va permettre d'ajouter/modifier les commandes Composer.
Par exemple, la commande require
va permettre d'installer des paquets particuliers appelés Recipes.
Une recipe est un paquet ne contenant qu'une liste de dépendances (via un composer.json
), ainsi
qu'une liste de modifications à effectuer (via un manifest.json
) :
Certains recipes sont officielles (= maintenues par la code team Symfony), d'autres contrib.
Elles sont toutes visibles sur https://flex.symfony.com.
Les fichiers manifest.json
des recipes sont lues via les Configurators.
Ex de fichier :
{
"bundles": {
"Alexandre\\EvcBundle\\AlexandreEvcBundle": ["all"]
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/"
},
"env": {
"EVC_API": "Enter the api version provided by evc.de support",
"EVC_USERNAME": "Enter your reseller account number",
"EVC_PASSWORD": "Enter your api password, not the password to connect on evc.de website"
}
}
Cette configuration nécessite par exemple 3 Configurators pour :
Les configurators natifs sont visibles ici.
De nouvelles commandes Composer ont été créées pour interagir avec Flex :
composer recipes
: liste les recipes disponibles (et affiche les mises à jour éventuelles)composer sync-recipes [--force]
: met à jour une recipeLorsqu'une erreur (PHP) est levée :
ErrorHandler
la transforme en exception, qui est levéeLorsqu'une exception est levée :
HttpKernel
l'attrape et dispatche un évènement kernel.exception
ErrorListener
effectue un forward vers l'ErrorController
ErrorController
retourne une Response contenant l'erreur formatée via le ErrorRenderer
La classe Event
contient deux méthodes :
stopPropagation()
: elle indique qu'on ne souhaite pas que les prochains listeners ne traitent l'évènementisPropagationStopped()
: elle indique si on a demandé à stopper la propagationÀ l'instar des services, on utilisait historiquement une chaîne de caractères comme identifiant de l'évènement
(ex: kernel.terminate
).
Depuis Symfony 5, on utilise directement le nom de sa classe (ex: Symfony\Component\Mailer\Event\MessageEvent
).
Pour lister les évènements disponibles et leurs listeners, utiliser la commande :
bin/console debug:event-dispatcher
Best practice :
Le subscriber est plus pratique d'utilisation, car il ne nécessite aucune configuration (en fichier yaml).
Il suffira qu'il implémente EventSubscriberInterface
.
Les évènements du Kernel sont visibles ici.
Il y a deux versions principales à connaitre :
La dernière version stable (ex: 5.1.8
) : elle inclue toutes les nouvelles fonctionnalités, corrections
de bogue et patchs de sécurité.
La version LTS (Long Term Support) (ex: 4.4.16
) : elle inclue toutes les dernières fonctionnalités
de la version majeure, ainsi que les nouvelles corrections (pendant 3 ans au total) de bogue et patchs de sécurité
(pendant 4 ans au total).
La version LTS est changée tous les 2 ans. Comme les versions mineures sortent tous les 6 mois, on a seulement 4 versions mineures par majeures.
Les versions non-LTS sont supportées 8 mois.
Note : en version 2.x, il y a eu plusieurs versions LTS
D'une version mineure à l'autre, Symfony garantie un fonctionnement à l'identique pour toute
l'API publique (= ni @internal
, ni @experimental
).
Les dépréciations indiquent qu'une fonction/classe/... ne devrait plus être utilisée grâce à l'annotation @deprecated
.
Comment surcharger un champ de formulaire (FormType) ?
On crée une extension de type.
Comment surcharger une route définie par un bundle ?
Il faut :
Comment surcharger un contrôleur ?
On peut :
Comment modifier un service ?
On peut :
service.yaml
) et le faire pointer vers un nouveau serviceLe framework est compatible avec de nombreuse PSR :
PSR-1/PSR-2 : coding standards
PSR-4 : namespaces et autoloading
PSR-3 : logger (via MonologBundle)
PSR-6/PSR-16 : cache
PSR-11 : conteneur de services
PSR-14 : dispatcheur d'évènements
HttpClient est compatible : PSR-7/PSR-17/PSR-18
HttpPlug est compatible avec l'implémentation : PSR-7
Publié le :
Cet article est une synthèse de la documentation officielle : Controller.
Qu'est-ce qu'un contrôleur ?
Son rôle est de transformer la Request
de l'utilisateur en une Response
à lui renvoyer.
Concrètement, c'est juste un callable.
On a l'habitude d'appeler contrôleur la classe contenant une action associée à une route,
mais en réalité c'est l'action en elle-même qui est un contrôleur.
Pour éviter cette incohérence, on peut n'avoir qu'une action par fichier contrôleur, nommée __invoke()
.
Comment Symfony sait-il ce qui est un contrôleur ?
C'est le fichier services.yaml
qui l'indique :
services:
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
Comme on le voit dans ce fichier, pas de contrainte sur le nommage de la classe ou de ses actions.
Tous les services dans src/Controller/
auront le tag controller.service_arguments
.
Qu'elles sont les particularités des contrôleurs, apportées via le tag controller.service_arguments
?
Ce tag permet à Symfony de :
public
(car ce sera un point d'entrée de l'application,
donc non appelé explicitement par nos services)ArgumentResolver
.
(Cf. classe RegisterControllerArgumentLocatorsPass
)
Un contrôleur doit-il retourner absolument une Response ?
Pas obligatoirement.ViewEvent
. Un listener pourra traiter cet évènement
pour forwarder vers un contrôleur en fallback.
Cette classe peut être héritée par une classe de contrôleur. (Cf. classe AbstractController
)
Elle fournit un certain nombre de raccourcis, pour accéder à des méthodes de certains services (cf. Service locator ci-après).
Il s'agit d'un proxy du Conteneur de services, contenant un sous-ensemble des services.
Par exemple, la propriété $container
d'AbstractController
en est un (et pas le vrai Conteneur).
Il donne accès à seulement certains services et arguments (cf. AbstractController::getSubscribedServices()
).
Quelle est différence entre forward et redirect ?
La redirection redirige l'utilisateur vers une nouvelle page et son navigateur fait une seconde requête.
Cela lui est donc visible. C'est une redirection HTTP.
Le forward exécute une seconde (sous-)requête directement, pour en retourner sa réponse.
L'utilisateur reçoit directement le résultat de cette seconde requête sans en avoir conscience.
C'est une redirection interne.
Note : Tous les listeners seront à nouveaux appelés, même sur la requête secondaire.
Par défaut, une redirection n'est pas permanente. Elle est conditionnée ponctuellement.
Par exemple, si on n'est pas connecté et qu'on essaie d'accéder à une page privée, on pourra
être redirigé vers la page de login. Ce n'est pas une redirection permanente, car une fois connecté, l'utilisateur
devra pouvoir y accéder.
Par défaut, le code HTTP est donc 302
. On peut le rendre permanent via le code301
.
Il existe également leurs pendants 308
(par défaut) et 307
.
Cela oblige de conserver la même méthode HTTP (GET, POST,...).
Pour effectuer une redirection dans un contrôleur, on peut utiliser plusieurs méthodes :
public function __invoke(): RedirectResponse
{
// Si on étend l'AbstractController
return $this->redirectToRoute('app_homepage');
// ou
return $this->redirect($this->generateUrl('app_homepage'));
// Ou comme on le ferait dans un contrôleur agnostique
return new RedirectResponse(
return $this->urlGenerator->generate('app_homepage')
);
}
Qu'est-ce qu'une HttpException
?
C'est une exception classique, dont le code correspond à un code HTTP (ex: 404, 403, ...).
Quand Symfony rencontre une erreur de ce type, il la transforme en une Response
évitant ainsi de retourner
une erreur 500 au client.
Symfony implémente beaucoup d'exceptions HTTP, visibles dans
Symfony\Component\HttpKernel\Exception
.
Documentation La Request contient des conteneurs de données appelés bags :
query
: paramètres d'URL (ex: ?foo=bar&bar=foo)request
: données envoyées en POSTcookies
files
: fichiers téléversésserver
: variables $_ENV et $_SERVERheaders
: variables $_SERVER['headers']attributes
: variables spécifiques au fonctionnement (ex: _route
)
Elle permet principalement d'accéder aux données dans ces conteneurs, mais propose également un certain
nombre de raccourcis, souvent pour accéder aux headers les plus communs.
Les valeurs de chaque bag sont accessibles de la même manière, via la même méthode get()
. Ex :
use Symfony\Component\HttpFoundation\Request;
/** @var Request $request */
$request->request->get('email');
Il existe des variantes permettant de filter/convertir la donnée directement :
Filtres (utilise la fonction filter()
de php derrière) :
getAlpha()
getAlnum()
getDigits()
Conversions :getInt()
getBoolean()
Comment envoyer un cookie au navigateur de l'utilisateur ?
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;
$response = new Response('A foo enters in a bar');
$response->headers->setCookie(
Cookie::create('last_visit', \time())
);
Comment supprimer un cookie du navigateur de l'utilisateur ?
use Symfony\Component\HttpFoundation\Response;
$response = new Response('A foo enters in a bar');
$response->headers->clearCookie('last_visit');
Note : il existe également removeCookie()
, qui permet (via un listener par exemple) de retirer un cookie
qui devait être ajouté via setCookie()
durant le traitement de la requête.
Documentation
Comment récupérer la session ?
Plusieurs manières possibles.
Elle est disponible dans le bag session
de la Request :
use Symfony\Component\HttpFoundation\Request;
/** @var Request $request */
$session = $request->getSession();
$emailInSession = $session->get('email');
Elle est disponible via le Service Locator d'AbstractController
:
$session = $this->get('session');
$emailInSession = $session->get('email');
On peut également l'injecter en tant que service (classe SessionInterface
).
Documentation
Un message flash est stocké dans la session.
Le principe est d'y stocker un message qui disparaitra dès qu'il est consommé/lu.
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
// Ajout d'un message flash
/** @var SessionInterface $session */
$session->getFlashBag()->add('success', 'You reach level 2!');
// ou via le raccourci d'AbstractController
/** @var AbstractController $this */
$this->addFlash('success', 'You reach level 2!');
// Consommation d'un message flash
/** @var SessionInterface $session */
$session->getFlashBag()->get('You reach level 2!');
Note :
Il existe également les fonctions peek()
et peekAll()
qui permettent de lire des messages sans les consommer.
Ce bag est accessible dans Twig via app.flashes
:
{% for message in app.flashes('notice') %}
<div class="flash-notice">
{{ message }}
</div>
{% endfor %}
Symfony fournit deux contrôleurs spécifiques :
TemplateController
:
pour servir automatiquement un template comme page statique.RedirectController
:
pour rediriger automatiquement une URL vers une autre.
Il est possible de personnaliser les pages d'erreurs HTTP Symfony (ex: 404, 403, ...). Note : Pour tester le rendu d'une de ces pages d'erreur sans devoir causer l'erreur, on peut utiliser une URL spécifique (ex pour l'erreur 403 : localhost:8080/_error/403).
Publié le :
Cet article est une synthèse de la documentation officielle : Twig.
Twig est un gestionnaire de templates, des fichiers contenant du texte et des variables qui seront remplacées à terme, lorsqu'on "rendra" le template, par des valeurs.
C'est un langage écrit en PHP, générant du PHP. Les templates seront transpilés en PHP.
La fonction la plus courante est le echo()
de PHP, représentée par {{ }}
dans Twig, pour afficher une valeur.
Comme pour les autres composants, on peut lister ses options de paramétrage disponibles via la commande :
bin/console config:dump twig
Un bloc est une partie de template nommée et réutilisable. On peut le considérer comme une fonction qui affiche du contenu.
Par défaut, les blocs sont affichés/exécutés/rendus dès qu'ils sont présents dans un template. On peut toutefois en appeler un explicitement comme une fonction :
use
), à la manière d'un Trait PHP{# Bloc dans le même template, ou son parent #}
{{ block('my_block') }}
{# Blocs déclarés dans un autre template, mais considéré comme faisant partie du contexte #}
{% use 'a_template_with_several_blocks.twig' %}
{{ block('custom_block_1') }}
{{ block('custom_block_2') }}
{{ block('custom_block_3') }}
{# Bloc déclaré dans un autre template, lequel est indiqué explicitement #}
{{ block('my_external_block', 'another_template.twig') }}
Comme beaucoup de langage permettant de créer des fonctions dans d'autres fonctions, Twig permet de créer des blocs dans des blocs.
Les blocs sont comme des fonctions, mais n'ont pas d'argument. Les variables disponibles sont celles du contexte.
Une macro est comme un bloc sauf :
Exemple :
{% macro my_macro(text) %}
Some text: {{ text }}
{% endmacro %}
{# Appel de la macro présente dans le contexte #}
{{ _self.my_macro('some text') }}
{# Appel dune macro présente dans un autre template #}
{% import 'my_macros.twig' as my_alias %}
{{ my_alias.some_external_macro() }}
L'héritage de templates fonctionne comme l'héritage de classes. Les blocs sont alors comme des méthodes publiques, dont on hérite et que l'on peut surcharger.
Pour déclarer un template comme enfant d'un autre, on utilise le tag extends
(comme pour une classe PHP) :
{% extends 'base.html.twig' %}
{# Pour vider le contenu d'un bloc parent, on le redéclare en le laissant vide #}
{% block title %}
{% endblock %}
{# Pour remplacer le contenu d'un bloc parent, on le redéclare avec le nouveau contenu souhaité #}
{% block title %}
My child title
{% endblock %}
{# Pour conserver le contenu d'un bloc parent mais y ajouter du contenu #}
{% block title %}
My child title | {{ parent() }}
{% endblock %}
Remarque : Spécificité Twig, un template enfant ne doit jamais afficher du contenu hors des blocs définis par son parent.
Pour expliquer cela, on peut considérer le parent comme une interface. Seuls ses blocs (= ses méthodes) sont connues. Quand on manipule les templates (= les objets) qui l'implémentent, on n'a connaissance que de celles-ci.
À l'instar d'un trait PHP, un bloc de template externe inclus au contexte (via le tag use
) peut être surchargé.
On peut alors vider, remplacer, ou modifier le contenu du bloc (toujours via la fonction parent()
) .
Une variable globale est disponible partout dans Twig, à tout moment. La liste de ces variables est visible avec la commande suivante :
bin/console debug:twig
Remarque : ces variables peuvent être surchargées par une variable locale au template (si elles ont le même nom).
Twig en fournit 3 par défaut :
_self
: le nom du template courant_context
: la liste des variables (locales) disponibles (celles passées dans le render()
côté PHP)_charset
: le charset courantSymfony (ou plutôt son Twig Bridge) en ajoute une pour nous : app
(documentation).
On peut en ajouter
# config/packages/twig.yaml
twig:
globals:
my_var: 'value'
my_param1: '%some_parameter%'
my_service: '@my_service'
my_array_var:
- 'Monday'
- 'Tuesday'
GlobalInterface
et sa méthode getGlobals()
.Pour inclure un template dans un autre, on utilise le tag include
, ou la fonction include()
:
{# Avec tout le contexte courant #}
{% include 'template.twig' %}
{{ include('template.twig') }}
{# Avec le contexte courant et des variables spécifiques #}
{% include 'template.twig' with {'my_var': 'some_value'} %}
{{ include('template.twig', {'my_var': 'some_value'}) }}
{# Sans le contexte courant #}
{% include 'template.twig' only %}
{{ include('template.twig', with_context = false) }}
{# Sans le contexte courant mais des variables spécifiques #}
{% include 'template.twig' with {'my_var': 'some_value'} only %}
{{ include('template.twig', {'my_var': 'some_value'}, with_context = false) }}
Il est considéré comme une bonne pratique de retirer le contexte courant lors de l'inclusion
L'avantage d'utiliser le tag, est de pouvoir facilement basculer vers embed
si on a besoin de plus de flexibilité
(cf. ci-après).
Pour modifier l'un des blocs du contenu inclus, il faut utiliser le tag embed
.
{# Avec tout le contexte courant #}
{% embed 'template.twig' %}
{% block my_block %}
This block has been overrided!
{% endblock %}
{% endembed %}
On peut retirer le contexte pour embed
de la même manière que include
.
La distinction entre les deux est la même que dans le monde bash Linux.
Un filtre est une fonction, recevant au moins un argument.
{{ some_function() }}
{{ some_function_with_args('arg1', 'arg2') }}
{{ 'a string as argument'|some_filter }}
{{ 'a string as argument'|some_filter_with_more_args('arg2') }}
Les filtres permettent de chainer facilement plusieurs traitements sans que la variable initiale soit perdue dans la notation :
{# avec filtres, on lit de gauche à droite #}
{{ 'mon texte'|gras|italique|color('red')|majuscule }}
{# avec fonctions, on lit de l'intérieur vers l'extérieur #}
{{ majuscule(color(italique(gras('mon texte)), 'red')) }}
Ces opérateurs ajoutent du sucre syntaxique pour les if
des templates.
Exemples (in
, is
, matches
, starts
, end
) :
{% if 'monday' in my_day_list %}{% endif %}
{% if 'a' in my_string %}{% endif %}
{% if my_var is defined %}{% endif %}
{% if my_var matches '/some_regex/' %}{% endif %}
{% if my_var starts with 'my_prefix' %}{% endif %}
{% if my_var not ends with 'my_suffix' %}{% endif %}
Il est possible d'en ajouter dans une extension twig, en implémentant la méthode getOperators()
.
Les tests ajoute du sucre syntaxique pour les if
des templates contenant l'opérateur logique is
.
Exemples (defined
, empty
, iterable
, odd
) :
{% if my_var is defined %}{% endif %}
{% if my_var is empty %}{% endif %}
{% if my_var is iterable %}{% endif %}
{% if my_var is odd %}{% endif %}
{% if my_var is sameas(some_var) %}{% endif %}
Il est possible d'en ajouter dans une extension twig, en implémentant la méthode getTests()
.
Une extension Twig permet d'ajouter des filtres et fonctions personnalisés, mais également des tags, des opérateurs logiques, des tests, ...
Les filtres (TwigFilter
) et fonctions (TwigFunction
) que l'on déclare sont des objets qui associent un nom à un
Callable. Lorsqu'on utilisera ce nom dans un template, le Callable sera appelé.
Remarque : par défaut, toutes fonctions ou filtres ajoutés verra sa valeur de retour échappée.
Pour éviter cela, on peut indiquer ['is_safe' => ['html']]
par exemple lors de leur déclaration.
Si une extension a besoin d'un service pour le traitement de l'un de ses filtres ou fonctions, il faut éviter d'ajouter cette dépendance à l'extension, par soucis de performance.
En effet, si on l'injecte dans le constructeur de l'extension, il sera instancié très tôt durant l'exécution : dès l'enregistrement de l'extension.
Pour éviter ça, on peut déplacer le code nécessitant la dépendance dans un service dédié,
implémentant TwigE\Extension\RuntimeExtensionInterface
.
On utilisera ensuite le nom de ce service comme "instance" de Callable, et twig l'instanciera automatiquement au bon moment :
use \Twig\TwigFilter;
use \App\Twig\MyServiceRuntime;
public function getFilters(): array {
return [
new TwigFilter('my_filter', [MyServiceRuntime::class, 'methodOfServiceToCall']),
];
}
Le service ne sera instancié qu'à l'appel du filtre ou de la fonction, et pas avant. Il est donc logique de créer un service par dépendance.
La convention utilisée par les extensions du Twig Bridge est de les ranger sous le namespace \App\Twig
avec les extensions, et de les suffixer avec Runtime
.
L'échappement permet à du texte de ne pas être interprêté par un langage.
Dans Twig, par défaut toute variable affichée (ex: {{ my_variable }}
) est échappée.
Toutes ces syntaxes sont équivalentes :
{{ my_var }}
{{ my_var|e }}
{{ my_var|escape }}
{{ my_var|escape('html') }}
Explication : e
est un alias au filtre escape
, qui reçoit par défaut la valeur html
.
Le filtre escape
accepte les valeurs suivantes en argument : html
, js
, css
, url
, html_attr
.
Le dernier permet d'échapper les attributs des balises HTML.
Le filtre raw
permet d'annuler l'échappement de la valeur.
{{ my_var|raw }}
Une autre technique consiste à changer l'extension du template twig.
Si votre template s'appelle my_template.js.twig
, alors escape
recevra js
pour valeur.
Si toutefois le filtre ne reconnais pas l'extension, html
sera choisi.
Imaginons qu'on souhaite afficher le nom de l'utilisateur courant en haut de chaque page de l'application.
Tous nos templates héritent d'un même parent qui contient un bloc affichant cette variable.
Pour que l'utilisateur courant soit passé au template, la solution de base consiste à l'ajouter dans le
tableau des variables lors du render()
. Cela implique par contre de devoir la passer pour les rendus de tous les
templates enfants et vient complexifier le contrôleur qui demande le rendu.
La solution consiste à créer un contrôleur dédié au rendu de cette variable uniquement, qui sera lui-même directement rendu dans le template :
{{ render(controller('App\\Controller\\MyController::myAction', {})) }}
Explications :
render()
appelle une URL et en affiche la réponsecontroller()
retourne la réponse du contrôleur en argumentUtiliser le rendu de contrôleur est considéré comme une bonne pratique.
On peut la privilégier pour chaque bloc du design mutualisé entre plusieurs pages.
L'inclusion de contrôleur permet de bénéficier du Cache HTTP via les fragments.
Pour cela, il faut remplacer render()
par render_esi()
, et activer son utilisation dans le framework.yaml
.
Pour traduire une chaîne, on peut utiliser le filtre trans
ou le bloc {% trans %}{% endtrans %}
.
Le premier échappe la chaîne.
Pour faire varier la traduction selon une variable (ex: le nombre ou le genre), on peut utiliser ICU.
Pour que le service de traduction utilise bien cette bibliothèque système, il faut ajouter le suffixe
+intl-icu
au nom du fichier de traduction (ex: messages+intl-icu.fr_FR.yaml
).
Depuis PH7.4 et l'arrivée des arrow functions, le filtre filter
accepte des fonctions anonymes :
{% for item in items|filter(i => i.relevent) %}
<p>{{ item.name }}</p>
{% else %}
<p>No item</p>
{% endfor %}
Le filtre format()
disponible dans les templates équivaut au sprintf()
de PHP.
{{ 'My string with %d word(s).'|format(5) }}
La fonction dump()
comme dans PHP affiche le contenu d'une variable (ou de toutes celles disponibles
si on l'appelle sans argument).
Pour les collecter mais ne pas les afficher dans le contexte (et seulement les voir via la debugtoolbar),
on peut utiliser le tag dump
à la place (ie. {% dump my_var %}
)
La fonction logout_path()
a été ajoutée pour générer une URL de logout directement.
Il faut ajouter la route dans le security.yaml
.
Beaucoup de fonctions ont été ajoutées pour formater une heure, une date, un nombre, ...
Elles sont toutes préfixées par format_
.
Elles sont fournies par IntlExtension
.
Publié le :
Cet article est une synthèse de la documentation officielle : Installing & Setting up the Symfony Framework.
Pour faciliter l'installation et le développement avec Symfony, téléchargez
l'exécutable symfony
.
La première fois que vous lancez exécutez symfony
, il vérifie automatiquement les prérequis
et vous propose de l'ajouter aux exécutables système.
L'installation consiste à initialiser un nouveau projet Symfony.
Deux choix principaux s'offrent à vous :
symfony new my_project_name --full
# Ou, sans l'exécutable, juste avec Composer
composer create-project symfony/website-skeleton my_project
symfony new my_project_name
# Ou, sans l'exécutable, juste avec Composer
composer create-project symfony/skeleton my_project
Si vous ne souhaitez pas la version courante de Symfony, vous pouvez en spécifier une autre :
symfony new my_project --version=4.4
# Ou, sans l'exécutable, juste avec Composer
composer create-project symfony/skeleton:"^4.4" my_project
Note : vous pouvez également remplacer le numéro de version par lts
ou next
.
Si vous récupérez un projet Symfony depuis un gestionnaire de version - au hasard, Git - il vous faut juste téléchargez les dépendances via Composer :
cd my_projects
git clone [...]
cd my_project/
composer install
Généralement, les configurations par défaut sont définies dans le fichier .env
, et
celles propres à l'environnement doivent être surchargées dans un fichier .env.local
(tous deux à la racine).
Dans ce dernier, on retrouvera uniquement les propriétés du premier que l'on souhaite modifier.
Typiquement, on y indiquera les informations de connexion à la base de données.
Les répertoires <my_project>/var/cache/
et <my_project>/var/log/
doivent être accessibles en écriture.
(Plus d'informations ici : Setting up or Fixing File Permissions.)
De plus, si vous souhaitez utiliser la console symfony (bin/console
) plus facilement, vous devez la rendre
exécutable :
cd my-project/
chmod +x bin/console
Remarque : Cela n'est pas nécessaire si vous l'utilisez à travers PHP (ex : php bin/console about
).
En phase de développement, on peut se passer d'un serveur web classique comme apache ou nginx.
À la place, l'exécutable symfony
peut en lancer un pour nous :
cd my-project/
symfony server:start
Plus d'informations ici : Symfony Local Web Server
Si vous préférez un serveur web classique, voici des exemples pour les configurer : Configuring a Web Server
Si vous souhaitez ajouter des composants Symfony prêts à l'emploi, vous pouvez
en récupérer les bundles avec Composer.
Symfony lui a ajouté le plugin Flex, pour vous éviter d'avoir à les configurer.
De plus, cela vous permet d'installer un composant sans savoir où le trouver. Par exemple, vous avez besoin d'un logger ? Lancez la commande :
cd my-project/
composer require logger
Vous souhaitez un débogueur en mode dev ? Lancez la commande :
cd my-project/
composer require --dev debug
Pour vérifier la présence de failles connues parmi les dépendances de votre projet,
symfony
propose une commande :
symfony check:security
Publié le :
C4 est une proposition d'organisation graphique pour représenter une architecture logicielle sous forme de schémas.
Documentation : https://c4model.com
Le premier principe propose d'utiliser différentes échelles, pour différents schémas, plutôt qu'un seul, tentaculaire et incompréhensible.
Les 4 échelles, de la plus générale à la plus détaillée constituent les 4C.
C'est le niveau le plus haut, le contexte : ce qui se passe autour de l'application.
Celle-ci est représentée en un bloc au centre, autour duquel gravitent :
C'est le niveau représentant les différentes briques logicielles de l'application : les conteneurs.
C'est un peu le même diagramme que le précédent, mais avec un zoom dans la boîte centrale.
Exemples de conteneurs :
Les composants représentent les différents besoins fonctionnels auxquels répond un conteneur.
Un schéma de ce niveau permet de visualiser l'organisation générale du conteneur.
Par exemple, on pourra trouver :
Cela correspond souvent aux premiers niveaux de namespace/package/répertoires/etc.
C'est le niveau qui zoome sur l'un des composant. Il est beaucoup plus anecdotique que les précédents, et pourrait représenter un diagramme de classes, par exemple.
Le but du modèle n'est pas d'imposer un nombre d'échelles. De toute façon, et particulièrement en informatique, tout est poupées russes. On peut toujours zoomer et créer un nouveau schéma augmentant le niveau de détails.
Le but est plutôt de savoir découper ses schémas pour qu'ils restent pertinents et lisibles.
C'est ainsi seulement, qu'ils ajoutent de la valeur.
Le second aspect important du modèle soutien ce même objectif. On peut le résumer ainsi : un schéma doit se suffire à lui-même.
Il doit donc contenir suffisamment de texte (dans les blocs et sur leurs liaisons), un titre et une légende.
La comparaison avec une carte de géographie est très bien trouvée, car c'est exactement la même méthode. Une carte doit montrer quelque chose, sans besoin d'une page d'explication à côté.
Pour faciliter la création de schémas, le site officiel recense plusieurs outils, dont :
Publié le :
TortoiseSVN permet de générer des patchs pour passer d'une version à une autre du projet.
Il y a deux types de patch :
Le premier type génère un fichier .patch
, qui ressemble à quelque chose comme ça :
Index: settings/siteaccess/bo/site.ini.append.php
===================================================================
--- settings/siteaccess/bo/site.ini.append.php (révision 864)
+++ settings/siteaccess/bo/site.ini.append.php (copie de travail)
@@ -37,4 +37,23 @@
CachedViewPreferences[full]=admin_navigation_content=1;admin_children_viewmode=list;admin_list_limit=1
TranslationList=
+[DebugSettings]
+DebugOutput=enabled
+DebugToolbar=enabled
+DebugRedirection=disabled
+
+[TemplateSettings]
+Debug=disabled
+
+[DatabaseSettings]
+SQLOutput=disabled
+
*/ ?>
\ No newline at end of file
On peut y voir pour chaque fichier modifié, les lignes ajoutées et supprimées depuis le dernier commit.
Le second type génère l'arborescence et les fichiers entiers.
La liste des fichiers modifiés apparait, et on peut voir pour chacun d'eux les lignes impactées. On peut alors choisir de patcher tous les fichiers ou seulement certains.
La liste des fichiers modifiés entre les deux versions apparaît. Il suffit alors de :