On Github Sorendil / AmiSymfonyPresentation
avouez... vous regrettez déjà de ne pas l'avoir connu plus tôt.
Une présentation par Anthony Rossi / AMI Software Montpellier, le soleil, tout ça...
Un des buts principaux d'un framework est de garder le code organisé et permettre à l'application d'évoluer facilement au fil du temps en évitant le mélange des appels de BDD, HTML et autres codes de PHP dans le même script.
Boite à outils : On peut comparer un framework à un ensemble de briques. (= composant).
Un framework est au développeur ce que la boite à outils est au bricoleur.
Automatisation des tâches récurrentes : Pour symfony par exemple, il y a un composant Routing, Validation, etc.
Méthodologie: On développe à l'aide d'un langage. On apprend une syntaxe, des fonctions, des mots-clés... Toute une grammaireC'est là que le framework intervient ! Il énonce des conventions d’écriture et d’organisation destinées à rendre plus efficace, en homogénéisant et clarifiant. Il s’inspire des bonnes pratiques déjà existantes, notamment en termes de style. Enfin, il structure et favorise la discipline du code produit, et son indépendance à l’égard de toute solution logicielle ou matérielle.
Framework PHP !
En tant que dev, lorsque l'on développe une application Symfony, notre réponsabilité est d'écrire un code qui mappe la requête utilisateur (http....) à une ressource associée (la page d'Accueil, par exemple.)
Le code à exécuter (point d'entrée) est une méthode, appelée "Action" d'une classe, appelée "Contrôleur"
Routing qui redirige vers la bonne méthode
Le dev n'a plus qu'à créer la response.
Maintenant vous savez que le but de chaque app web est d'interpréter chaque requête afin de créer une réponse appropriée.
Quand une application s'élargit, il devient de plus en plus difficile de maintenant un code organisé et maintenable.
Systématiquement, les mêmes tâches complexes se répètent encore et encore : persistence de données dans la base, rendu et réutilisation des templates, gestion des soumissions de formulaires, envoi d'emails, validation des données utilisateur, gestion de la sécurité...
Félicitations ! Symfony est là pour vous.
Du coup, c'est quoi Symfony ? Symfony est avant tout une collection de plus de 20 librairies indépendants qui peuvent être utilisées dans n'importe quel projet PHP. Ces librairies, appelées "Composants Symfony".
HttpFoundation: Contient les classes "Request" et "Response" ainsi que d'autres classes utiles pour la gestion des sessions ou de l'upload des fichiers.
Routing: Système de routing puissant et rapide qui permet de mapper une URI spécifique (ex: /capitalized-documents) à des informations concernant la façon dont la requête doit être gérée (ex, exécuter cgetCapitalizedDocumentsAction()).
FORM: Le composant form est un outil qui aide à résoudre le problème de permettre les utilisateurs à interragir avec les données et modifier les données dans l'application. Bien que, traditionnellement ce fut géré via les formulaires HTML, le composant se concentre sur le traitement de la données vers et depuis le client et l'application, que la donnée soit récupérée via un formulaire HTML ou via une API.
Validator: Un système pour créer des règles de validation pour les données afin de les valider.
Templating: Une boîte à outils pour le rendu des templates, qui gère les héritages et qui effectue d'autres tâches communes.
Security: Une puissante librairie pour gérer tous types de sécurité dans une application.
Translation: Un composant pour traduire des chaînes de caractères.
Conclusion: Chacun de ces composants est découplé et peut être utilisé dans n'importe quel projet PHP, avec ou sans le framework Symfony.
2 tâches :
Du coup, c'est quoi Symfony Framework ? Symfony Framework est une librairie PHP qui rempli deux missions :
Chaque application est une combinaison de code et de configurations qui indique comment l'application se comporte.
Par exemple, le niveau d'erreur change de prod à dev.
Dans Symfony, l'idée des environnements est l'idée que le même code peut être lancé selon des configurations différentes.
Par exemple, l'environnement dev peut utiliser une configuration rendant le développement plus facile tandis que l'environnement de prod permettra d'optimiser l'application pour la rapidité.
<?php use Symfony\Component\HttpFoundation\Response; public function helloAction() { return new Response('Hello world!'); }
Un contrôleur est une fonction appelable qui récupère l'information de la requête HTTP afin de créer et retourner une réponse HTTP (grâce à l'objet Response).
La réponse peut être une page HTML, un XML, du Json, une image, une redirection, un poney, etc.
[Donner des exemples : récupération d'un article, création d'une source, ...]
namespace NinjaFactory\GameBundle\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class GameController extends Controller { public function indexAction(Request $request) { $games = $this->doctrine->getRepository(Game::class)->findAll(); return $this->render('game/index.html.twig', array( 'games' => $games, )); } }
game_show: path: /games/{slug} defaults: { _controller: NinjaFactory:GameBundle:Game:show } methods: [GET] article_list: path: /articles/{page} defaults: { _controller: NinjaFactory:BlogBundle:Aticle:list, page: 1 } requirements: page: \d+ methods: [GET, HEAD]
$uri = $this->get('router')->generate('game_show', array( 'slug' => 'halo-5' )); // /games/halo-5
<a href="{{ path('game_show', {'slug': 'halo-5'}) }}"> See Halo 5 page </a>
Comme vous le savez, le contrôleur est responsable de la gestion de chaque requête qui arrive. En réalité, le contrôleur délégue presque tout le travail afin que le code puisse être réutilisé et testé.
Quand un contrôleur doit générer du HTML et d'autres assets, il délègue au moteur de template.
Introduction TWIG
{# app/Resources/views/game/list.html.twig #} {% extends 'layout.html.twig' %} {% block body %} <h1>Last games created<h1> {% for game in games %} {{ include('game/game_list.html.twig', { 'game': game }) }} {% endfor %} {% endblock %}
Plugin-like: Un bundle ressemble à un plugin, un composant.
Tout est bundle: Le coeur de Symfony ainsi que le code que vous écrivez fait partie d'un bundle.
Les bundles sont la ressource principale de Symfony.
Cela permet d'utiliser des fonctionnalités pré-construites dans des bundles externes.
Un bundle est un ensemble structuré de fichiers: Un bundle est un répertoire contenant une collection de fichiers qui implémentent une fonctionnalité.
Par exemple, on pourrait avoir un BlogBundle, ForumBundle, UserBundle, etc.
...de différents types: Fichiers PHP, Templates, Stylesheets, JS, tests, etc.
Donner exemple d'héritage de bundle externe (comme avec FosUser)
// app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new AppBundle\AppBundle(), ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } return $bundles; }
Expliquer que les bundles dépendent de l'environnement.
Donner exemple d'utilisation de bundle (par exemple bundle payant)
La structure d'un bundle est simple et flexible.
Par défaut, un bundle suit des conventions afin de garder le code cohérent entre les bundles...
Mais ce n'est pas obligatoire, on peut créer des dossiers où on veut, les fichiers auront pour namespace l'arborescence du fichier dans le bundle.
Resource/public/: Copié par lien symbolique ou non.
Une tâche très commune et redondante pour chaque application est de pouvoir persister et lire l'information de/vers une Base de données.
ORM: Un ORM (Object-relational mapping, ou Mapping objet-relationnel en français) est une couche d'abstraction de la base de données visant à créer l'illusion d'une base de données orientée objet à partir d'une base de données relationnelle. L'ORM définit des correspondances entre la base de données et les objets du langage en question, les objets PHP pour notre cas.
Dans cette partie, on apprendra la philosophie de base derrière Doctrine.
// src/AppBundle/Entity/Product.php namespace AppBundle\Entity; class Product { private $name; private $price; private $description; // Getters and Setters here (can be auto generated by doctrine command) }
Supposez qu'on crée une application où des produits ont besoin d'être affichés.
Sans penser à Doctrine ni aux BDD, on souhaiterait avoir un objet Product qui représente ces produits.
La classe affichée est appelée "Entité"
Pour l'instant, notre classe n'est qu'une classe.
Doctrine allows you to work with databases in a much more interesting way than just fetching rows of scalar data into an array. Instead, Doctrine allows you to fetch entire objects out of the database, and to persist entire objects to the database. For Doctrine to be able to do this, you must map your database tables to specific PHP classes, and the columns on those tables must be mapped to specific properties on their corresponding PHP classes.
// src/AppBundle/Entity/Product.php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="product") */ class Product { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=100) */ private $name; /** * @ORM\Column(type="decimal", scale=2) */ private $price; /** * @ORM\Column(type="text") */ private $description; // Getters and Setters here }
// src/AppBundle/Controller/DefaultController.php // ... use AppBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; // ... public function createAction() { $product = new Product(); $product->setName('Keyboard'); $product->setPrice(19.99); $product->setDescription('Ergonomic and stylish!'); $em = $this->getDoctrine()->getManager(); // tells Doctrine you want to (eventually) save the Product (no queries yet) $em->persist($product); // actually executes the queries (i.e. the INSERT query) $em->flush(); // After the flush, product has an ID ($product->getId()) return new Response('Saved new product with id '.$product->getId()); }
Explications
La création et la modification fonctionnent de la même manière, permettant de regrouper les parties du code concernant l'écriture des objets.
public function showAction($productId) { $product = $this->getDoctrine() ->getRepository('AppBundle:Product') ->find($productId); if (!$product) { throw $this->createNotFoundException( 'No product found for id '.$productId ); } // ... do something, like pass the $product object into a template }
// query for a single product by its primary key (usually "id") $product = $repository->find($productId); // dynamic method names to find a single product based on a column value $product = $repository->findOneById($productId); $product = $repository->findOneByName('Keyboard'); // dynamic method names to find a group of products based on a column value $products = $repository->findByPrice(19.99); // find *all* products $products = $repository->findAll(); // query for a single product matching the given name and price $product = $repository->findOneBy( array('name' => 'Keyboard', 'price' => 19.99) ); // query for multiple products matching the given name, ordered by price $products = $repository->findBy( array('name' => 'Keyboard'), array('price' => 'ASC') );
public function updateAction($productId) { $em = $this->getDoctrine()->getManager(); $product = $em->getRepository('AppBundle:Product')->find($productId); if (!$product) { throw $this->createNotFoundException( 'No product found for id '.$productId ); } $product->setName('New product name!'); $em->flush(); return $this->redirectToRoute('homepage'); }
Explications
Ici, pas de persist, puisque le persist annonce juste à Doctrine de "watch" l'objet.
$em->remove($product); $em->flush();
Explications
Ici, pas de persist, puisque le persist annonce juste à Doctrine de "watch" l'objet.
$em = $this->getDoctrine()->getManager(); $query = $em->createQuery( 'SELECT p FROM AppBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' )->setParameter('price', '19.99'); $products = $query->getResult(); // Return array of Product objects
Nous avons déjà vu comment effectuer des requêtes simples à l'aide des méthodes de bases, mais il est bien sûr possible de créer ses propres requêtes plus complexes.
Si vous êtes habitué au SQL, alors le DQl vous semblera naturel. La grosse différence est que vous avez besoin de penser "objet" au lieu de rows de la BDD. Par exemple, vous sélectionnez les données de "AppBundle:Product" et pas du nom de la table.
$em = $this->getDoctrine()->getManager(); // createQueryBuilder automatically selects FROM AppBundle:Product // and aliases it to "p" $query = $repository->createQueryBuilder('p') ->where('p.price > :price') ->setParameter('price', '19.99') ->orderBy('p.price', 'ASC') ->getQuery(); $products = $query->getResult(); // to get just one result: // $product = $query->setMaxResults(1)->getOneOrNullResult();
La requête effectue la même chose qu'avec l'exemple en DQL précédent.
Contrairement au SQL, QueryBuilder est une API pour construire des requêtes. C'est donc plus facile pour construire des requêtes dynamiques comme itérer sur des paramètres ou des filtres.
Puisqu'il construit la requête à notre place, aucune adaptation de code sera nécessaire pour les requêtes si on passe de MySQL à MongoDB, par ex.
Quand on affiche une page en mode dev, le profiler Symfony est câblé avec Doctrine ce qui nous permet de voir combien de requêtes ont été effectuées.
Si la page affiche beaucoup de requêtes, pour une seule page, il est peut-être temps de regarder si il n'y a pas de problèmes d'optimisations.
// src/AppBundle/Entity/Category.php // ... use Doctrine\Common\Collections\ArrayCollection; class Category { // ... /** * @ORM\OneToMany(targetEntity="Product", mappedBy="category") */ private $products; public function __construct() { $this->products = new ArrayCollection(); } }
Supposez que notre objet produit appartient maintenant a exactement une catégorie. Dans ce cas, on voudrait un objet "Category" qui soit relié à notre objet "Product".
// src/AppBundle/Entity/Product.php // ... class Product { // ... /** * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") * @ORM\JoinColumn(name="category_id", referencedColumnName="id") */ private $category; }
Expliquer les annotations OneToMany et ManyToOne
[L'annotation JoinColumn sera expliqué dans la page suivante.]
Ignore the Doctrine metadata for a moment. You now have two classes - Category and Product with a natural one-to-many relationship. The Category class holds an array of Product objects and the Product object can hold one Category object. In other words - you've built your classes in a way that makes sense for your needs. The fact that the data needs to be persisted to a database is always secondary. Now, look at the metadata above the $category property on the Product class. The information here tells Doctrine that the related class is Category and that it should store the id of the category record on a category_id field that lives on the product table. In other words, the related Category object will be stored on the $category property, but behind the scenes, Doctrine will persist this relationship by storing the category's id value on a category_id column of the product table.
// ... use AppBundle\Entity\Category; use AppBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller { public function createProductAction() { $category = new Category(); $category->setName('Computer Peripherals'); $product = new Product(); $product->setName('Keyboard'); $product->setPrice(19.99); $product->setDescription('Ergonomic and stylish!'); // relate this product to the category $product->setCategory($category); $em = $this->getDoctrine()->getManager(); $em->persist($category); $em->persist($product); $em->flush(); return new Response( 'Saved new product with id: '.$product->getId() .' and new category with id: '.$category->getId() ); } }
Now, a single row is added to both the category and product tables. The product.category_id column for the new product is set to whatever the id is of the new category. Doctrine manages the persistence of this relationship for you.
$product = $productRepository->find($productId); $categoryName = $product->getCategory()->getName();
Lorsque l'on a besoin de récupérer des objets via des associations, on procède de la même manière qu'avant.
La récupération de la catégorie est implicite grâce à getCategory().
Décrire le workflow en pensant aux proxies, optimisation de requêtes, ...
Deux autres façons de récupérer la catégorie liée au produit sans extra requetes :
// src/AppBundle/Entity/Product.php /** * @ORM\PrePersist */ public function setUpdatedDate() { $this->updatedDate = new \DateTime(); }
Events disponibles : preRemove, postRemove, prePersist, postPersist, preUpdate, postUpdate, postLoad, loadClassMetadata, onClassMetadataNotFound, preFlush, onFlush, postFlush, onClear.
Parfois, vous voulez effectuer une action juste avant qu'une entité soit enregistrée ou supprimée.
Ces types d'actions sont des "lifecycle callbacks" puisque ce sont des méthodes de callback qui seront exécutées durant différentes étapes du cycle de vie d'une entité.
Exemple d'utilisation : création de slug automatique.
php bin/console doctrine:database:create php bin/console doctrine:generate:entity php bin/console doctrine:generate:entities AppBundle/Entity/Product php bin/console doctrine:schema:update --force
1. Par exemple, les données issues des formulaires ont besoin d'être validées. Les données ont aussi besoin d'etre validées avant d'etre écrire en BDD (255 caractères max par ex)
// src/AppBundle/Entity/Author.php // ... use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() */ public $name; }
// ... use Symfony\Component\HttpFoundation\Response; use AppBundle\Entity\Author; public function authorAction() { $author = new Author(); // ... do something to the $author object $validator = $this->get('validator'); /** @var \Symfony\Component\Validator\ConstraintViolationList */ $errors = $validator->validate($author); if (count($errors) > 0) { /* * Uses a __toString method on the $errors variable which is a * ConstraintViolationList object. This gives us a nice string * for debugging. */ $errorsString = (string) $errors; return new Response($errorsString); } return new Response('The author is valid! Yes!'); }
if (count($errors) > 0) { return $this->render('author/validation.html.twig', array( 'errors' => $errors, )); }
{# app/Resources/views/author/validation.html.twig #} <h3>The author has the following errors</h3> <ul> {% for error in errors %} <li>{{ error.message }}</li> {% endfor %} </ul>
BON A SAVOIR : Le service validator permet de valider un objet quand on veut.
En réalité, la validation sera pour le plus souvent utilisée avec les formulaires, permettant de valider que la donnée renseignée par l'utilisateur est correcte.
Le composant formulaire de SF utilise en interne le composant validator, lui permettant d'effectuer cette tâche.
Symfony inclu directement les contraintes les plus utilisées :
Bien sûr, nous pouvons créer nos propres validations... !
// src/AppBundle/Form/Type/TaskType.php namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\SubmitType; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task', TextType::class) ->add('dueDate', DateType::class, array('widget' => 'single_text')) ->add('save', SubmitType::class, array('label' => 'Create Task')) ; } }
{# app/Resources/views/default/new.html.twig #} {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }}
Décrire les fonctions TWIG
Chaque utilisateur a des rôles, qui est une collection de string. Par exemple, ROLE_ADMIN, ROLE_COMMENT_WRITE, ...
# app/config/security.yml security: # ... access_control: # require ROLE_ADMIN for /admin* - { path: ^/admin, roles: ROLE_ADMIN }
Utile pour sécuriser des sections entières
// ... public function helloAction($name) { // The second parameter is used to specify on what object the role is tested. $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!'); // Old way : // if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) { // throw $this->createAccessDeniedException('Unable to access this page!'); // } // ... }
On peut aussi sécuriser via des annotations ( @Security("has_role('ROLE_ADMIN')"))
Ou dans les templates ({% if is_granted('ROLE_ADMIN') %})
Deux options
// check for "view" access: calls all voters $this->denyAccessUnlessGranted('view', $post);
Imagine you are designing a blog where users can comment on your posts. You also want a user to be able to edit their own comments, but not those of other users. Also, as the admin user, you yourself want to be able to edit all comments.
Traductions définies en
use Symfony\Component\HttpFoundation\Response; public function indexAction() { $translated = $this->get('translator')->trans('app.comment.action.add'); return new Response($translated); }
# Obtao\ForumBundle\Resources\translations\messages.fr.yml app: # Bundle name comment: # Object name action: # Type: action (button, ...) add: Nouveau commentaire title: # Type: title (h1, h2...) listPage: Tous les commentaires # ...
PROCESS:
La locale de l'user courant est récupérée via la requête ou d'une autre manière (prefs par exemple)
Un catalogue de messages traduits est chargé grâce aux ressources définies pour la locale (fr_CA).
Les messages fallback sont aussi chargés si ils n'existent pas déjà.
Il en résulte un grand dictionnaire de traductions.
Si la clé de la traduction existe, alors le traducteur retourne la trad. Sinon, elle retourne le message original.
// src/AppBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank(message = "app.author.name.notBlank") */ public $name; }
$ php bin/console debug:translation fr AcmeDemoBundle
(($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2 );
$translator->transChoice( 'There is one apple|There are %count% apples', 10, array('%count%' => 10) );
'There is one apple|There are %count% apples'
'Il y a %count% pomme|Il y a %count% pommes'
'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf[ There are many apples'
1. La pluralisation est un sujet sensible car les règles peuvent être complexes. Par exemple, voici la représentation mathématique des règles de pluralisation en russe.
2. Puisque les traductions dépendent du nombre, il faut le passer en paramètre.
3. En anglais, la plupart des mots ont la forme singulière lorsqu'il y a exactement un objet et un pluriel sur les autres formes (0, 2...) Donc si count vaut 1, la première chaine sera utilisée.
3. En français, les règles sont différentes. La forme singulière est utilisée lorsque l'on a 0 ou 1 objet.
4. On peut aussi spécifier les intervales que l'on souhaite lors de la traduction
1. Un objet peut faciliter l'envoi de mail pendant qu'un autre permet de persister une information ou d'écrire des logs.
1. Le fait est qu'une application fait beaucoup de choses qui sont organisés dans des objets pour gérer chaque tâche.
Ce chapitre traîte d'un objet PHP spécial qui aide à instancier, organiser et récupérer plusieurs objets de l'application.
2. L'objet service container permet de standardiser et centraliser la manière dont les objets sont instanciés.
3. A apprendre et à développer
4. Permet de réutiliser et de découpler le code plus facilement
Toutes les classes symfony utilisent le service container, permettant d'étendre, configurer ou utiliser n'importe quel objet en Symfony.
use AppBundle\Mailer; $mailer = new Mailer('sendmail'); $mailer->send('ryan@example.com', ...);
La classe Mailer permet d'envoyer un mail.
Mais que fait-on si on veut utiliser cet objet autre part ? On veut pas répeter l'instanciation ni la configuration.
Et si on change le type de transport d'email ? On est obligé de changer l'instanciation sur chaque appel !
Expliquer inversion de contrôle et injection de dépendance
# app/config/services.yml services: app.mailer: class: AppBundle\Mailer arguments: [sendmail]
class HelloController extends Controller { // ... public function sendEmailAction() { // ... $mailer = $this->get('app.mailer'); $mailer->send('ryan@foobar.net', ...); } }
Lors de la demande, le container construit l'objet et le retourne.
Un service n'est jamais construit jusqu'à ce qu'on l'appelle.
Une seule instance !
# app/config/services.yml parameters: app.mailer.transport: sendmail services: app.mailer: class: AppBundle\Mailer arguments: ['%app.mailer.transport%']
# app/config/services.yml services: app.mailer: # ... app.newsletter_manager: class: AppBundle\Newsletter\NewsletterManager arguments: ['@app.mailer']
// src/AppBundle/Newsletter/NewsletterManager.php namespace AppBundle\Newsletter; use AppBundle\Mailer; class NewsletterManager { protected $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } // ... }
services: app.mailer: # ... app.newsletter_manager: class: AppBundle\Newsletter\NewsletterManager calls: - [setMailer, ['@app.mailer']]
// src/AppBundle/Newsletter/NewsletterManager.php namespace AppBundle\Newsletter; use AppBundle\Mailer; class NewsletterManager { protected $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } // ... }
services: newsletter_manager: class: Acme\HelloBundle\Newsletter\NewsletterManager arguments: ["@request_stack"]
namespace Acme\HelloBundle\Newsletter; use Symfony\Component\HttpFoundation\RequestStack; class NewsletterManager { protected $requestStack; public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; } public function anyMethod() { $request = $this->requestStack->getCurrentRequest(); // ... do something with the request } // ... }
# app/config/services.yml services: app.newsletter_manager: class: AppBundle\Newsletter\NewsletterManager arguments: ['@?app.mailer']
public function __construct(Mailer $mailer = null) { // ... }
# app/config/services.yml services: foo.twig.extension: class: AppBundle\Extension\FooExtension public: false tags: - { name: twig.extension }
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->has('acme_mailer.transport_chain')) { return; } $definition = $container->findDefinition( 'acme_mailer.transport_chain' ); $taggedServices = $container->findTaggedServiceIds( 'acme_mailer.transport' ); foreach ($taggedServices as $id => $tags) { $definition->addMethodCall( 'addTransport', array(new Reference($id)) ); } } }
/** * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") */ private $created; /** * @Gedmo\Blameable(on="create") * @ORM\Column(type="string") */ private $createdBy;
Cité dans la doc officielle