SlideShare una empresa de Scribd logo
1 de 40
Descargar para leer sin conexión
DE LEGACY À SYMFONY
SYMFONY MONTRÉAL
16 JUIN 2016
Etienne Lachance
@elachance
QUI SUIS-JE
Sys admin de formation
Programmeur depuis ~10 ans
Propriétaire de elcweb.ca
Consultation en entreprise
Programmation
Hébergement spécialisé
Fin de l'auto promotion
CONTEXT
Evolution d'un projet "Legacy" sans affecter la productivité mais en
introduisant les bonne pratique d’un nouveau framework.
PAR LEGACY J'ENTEND
peu/pas de documentation
peu/pas de tests
Code procédural
Code spaghetti
Duplication de code (copier-coller)
Include-ception
Couplage de responsabilité
... Non SOLID
OBJECTIF
Permet de refactoriser le code petit peu par petit peu mais d'avoir des
avantages rapidement.
ATTENTION!
IL EST FORTEMENT RECOMMANDÉ D’ÉCRIRE DES TESTS
AUTOMATISÉS.
PHPUnit
Behat
3 MÉTHODOLOGIES
1. PARALLEL
simple a implémenter (mod_rewrite)
aucune communication direct entre les 2 applications
utilisation de la BD ou Redis pour l'échange entre les 2 apps
peu/pas d'impacte sur l'application 1
2. PROXY
l'utilisateur voie uniquement une application (Symfony)
necessite plus de travail pour la mise en place
"wrapper" pour les requêtes a l'application Legacy
authenti cation
sécurité entre les 2 applications
peu/pas d'impacte sur l'application Legacy
3. INTÉGRATION
On veut changer la structure fondamental du code actuel.
une seule application
OPTION PRÉSENTÉ
QU'EST-CE QUE SYMFONY?
Symfony est *
une collection de composante
un framework applicatif
une philosophy
une communauté
Symfony est a la base un framework HTTP
GESTION DES REQUÊTE / RÉPONSE
source: http://symfony.com/what-is-symfony
EXEMPLE
STRUCTURE DE FICHIER
BONNE PRATIQUE: PLACER LE CODE A L'EXTERIEUR DU RÉPERTOIRE PUBLIQUE.
STRUCTURE RÉVISÉ
MODIFIONS LE CODE
EXEMPLE DE TYPE "INCLUDE-CEPTION"
<?php
// index.php
include("includes/common.php");
include("includes/config.php");
$mod = $_GET['mod'];
if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui',$mod))
$mod = "dashboard";
include ("modules/".$mod.".php");
aucun namespace
logique basé sur include() / require()
LEGACY CONTROLLER
namespace AppBundleController;
use ...;
class LegacyController extends Controller
{
/** @Route("/index.php") */
public function legacyAction()
{
// __DIR__ == 'src/AppBundle/Controller'
include __DIR__ . '/../includes/common.php';
include __DIR__ . '/../includes/config.php';
// @todo: renommé $mod pour $module
$mod = $_GET['mod'];
if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui', $mod)) {
$mod = "dashboard";
}
LEGACY CONTROLLER - SUITE
// Include module file
$filename = __DIR__ . '/../modules/' . $mod . '.php';
if (!file_exists($filename)) {
throw new NotFoundHttpException('Module ' . $mod . ' not found');
}
// ob_start: Turn on output buffering
ob_start();
include $filename;
return new Response(ob_get_clean());
}
}
RÉCAPITULATION
déplacer les chiers a l'extérieur du répertoire publique
véri er si le module exist
si le module n'existe pas, retourne une erreur 404
encapsuler les "echo" du code legacy dans un objet Response
PROCHAINE ÉTAPES
Authenti cation/Autorisation (incluant la session)
Isolation de la base de donnée (Repository)
Vue (Templates)
AUTHENTIFICATION/AUTORISATION
AUTHENTIFICATION
Qui es-tu ?
AUTORISATION
Quels sont les accès / droits
MAINTENANT DANS SYMFONY
UTILISATEUR
Implement UserInterface
<?php
namespace AppBundleSecurityUser;
use ...
class User implements UserInterface
{
private $username;
private $password;
private $salt
prirate $roles;
...
}
PROVIDER
Est responsable d'aller chercher l'utilisateur
Implement UserProviderInterface
namespace AppBundleSecurityUser;
use ...
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username) { ... }
public function refreshUser(UserInterface $user) { ... }
public function supportsClass($class)
{
return $class === 'AppBundleSecurityUserUser';
}
}
ENCODER
Responsable d'encoder et de valider un mot de passe
namespace AppBundleSecurityEncoder;
use SymfonyComponentSecurityCoreEncoderBasePasswordEncoder;
class LegacyMd5Encoder extends BasePasswordEncoder
{
public function isPasswordValid($encoded, $raw, $salt = null) :bool
{
return $this->comparePasswords(strtolower($encoded), strtolower($this->encodePassword($raw, $
}
public function encodePassword($raw, $salt = null) :string
{
return md5($raw . 'secret_global_a_l_application');
}
}
CONFIGURATION
app/con g/services.yml
services:
app.user_provider:
class: AppBundleSecurityUserUserProvider
app.security.encoder.md5:
class: AppBundleSecurityEncoderLegacyMd5Encoder
app/con g/security.yml
security:
encoders:
AppBundleSecurityUserUser:
id: app.security.encoder.md5
providers:
legacy:
id: app.user_provider
firewall:
main:
pattern: ^/
http_basic: ~
RECOMMANDATION
Utilisation d'un algorithme plus sécuritaire comme bcrypt ou sha512
Convertion des mots de passes "on the y"
DOCTRINE
Permet de représenter en Objet et non en tables.
DOCTRINE / REPOSITORY
Dans le contexte de Doctrine, un Repository est utilisé pour allez chercher
l'information
Centraliser les requêtes SQL
Isoler les requêtes du "controlleur"
Classi er par context d'objet (Utilisateur/Produit/Client)
LEGACY
<?php
// ...
$conn = mysql_connect($db_host, $db_user, $db_password);
if (!$conn) {
echo "Unable to connect to DB: " . mysql_error();
exit;
}
if (!mysql_select_db($dbname)) {
echo "Unable to select mydbname: " . mysql_error();
exit;
}
$sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($_GET['cat']).";"
LEGACY - SUITE
$result = mysql_query($sql);
if (!$result) {
echo "Could not successfully run query ($sql) from DB: " . mysql_error();
exit;
}
if (mysql_num_rows($result) == 0) {
echo "No rows found, nothing to print so am exiting";
exit;
}
$data = array();
while ($row = mysql_fetch_assoc($result)) {
$data = $row;
}
mysql_free_result($result);
GÉNÉRATION D'ENTITÉ A PARTIR D'UNE BASE DE DONNÉ
EXISTANTE
$ php bin/console doctrine:mapping:import --force AcmeBlogBundle xml
$ php bin/console doctrine:mapping:convert annotation ./src
$ php bin/console doctrine:generate:entities AcmeBlogBundle
http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
REPOSITORY
namespace AppBundleEntity;
use DoctrineORMEntityRepository;
class ProductRepository extends EntityRepository
{
public function findByCategory($categoryId)
{
$sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($categoryId
$stmt = $this->getEntityManager()->getConnection()->prepare($sql);
$stmt->execute();
return $stmt->findAll();
}
}
RawSQLTrait: https://gist.github.com/estheban/3eae41271f6cf5f3180a
UTILISATION DANS UN CONTROLLEUR
class ProductController extends Controller
{
/**
* @Route("/product.php/category/{id}")
*/
public function productByCategory(Category $category)
{
// throw 404 si pas de Catégorie trouvée
$entityManager = $this->getDoctrine()->getManager();
return $entityManager
->getRepository("AppBundle:Product")
->findByCategory($category->getId());
}
}
Voir: @ParamConverter
TEMPLATES
Ne pas convertir les templates pour le "Fun"
On peut retourner:
une réponse directement (aucun template)
un template en php
un template en twig (natif)
un template en smarty
un template en ...
EXEMPLE D'UN CONTROLLEUR QUI RETOURNE UNE RÉPONSE
BASÉ SUR UN TEMPLATE
public function indexAction()
{
// some logic to retrieve the blogs
$blogs = ...;
$this->render(
'AcmeBlogBundle:Blog:index.html.twig',
array('blogs' => $blogs)
);
}
EXEMPLE D'UN TEMPLATE EN TWIG
{# app/Resources/views/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}
{% block content %}
{% for blogPost in blogs %}
<h2>{{ blogPost.title }}</h2>
<p>{{ blogPost.body }}</p>
{% endfor %}
{% endblock %}
EXEMPLE DE SMARTY ET TWIG
protected function renderSmarty($template, $outputData)
{
$smarty = new SmartyEngine();
$smarty->assign($outputData);
return $this->render(
'@ElcwebLegacy/Default/default.html.twig',
['output' => $smarty->fetch($template)]
);
}
public function bobAction()
{
$outputData = ['foo' => 'bar'];
return $this->renderSmarty('bob.tpl', $outputData);
}
QUESTIONS ?
MERCI!
http://elcweb.ca
http://etiennelachance.com
@elachance
https://github.com/estheban
https://www.linkedin.com/in/elachance

Más contenido relacionado

La actualidad más candente

PHP5 - POO
PHP5 - POOPHP5 - POO
PHP5 - POO
mazenovi
 

La actualidad más candente (18)

Cours php & Mysql - 4éme partie
Cours php & Mysql - 4éme partieCours php & Mysql - 4éme partie
Cours php & Mysql - 4éme partie
 
Trucs et astuces PHP et MySQL
Trucs et astuces PHP et MySQLTrucs et astuces PHP et MySQL
Trucs et astuces PHP et MySQL
 
Bases de PHP - Partie 1
Bases de PHP - Partie 1Bases de PHP - Partie 1
Bases de PHP - Partie 1
 
Cours php & Mysql - 2éme partie
Cours php & Mysql - 2éme partieCours php & Mysql - 2éme partie
Cours php & Mysql - 2éme partie
 
Examen principal- php - correction
Examen principal- php - correctionExamen principal- php - correction
Examen principal- php - correction
 
Application web php5 html5 css3 bootstrap
Application web php5 html5 css3 bootstrapApplication web php5 html5 css3 bootstrap
Application web php5 html5 css3 bootstrap
 
php2 : formulaire-session-PDO
php2 : formulaire-session-PDOphp2 : formulaire-session-PDO
php2 : formulaire-session-PDO
 
Utilisation optimale et professionnelle de PHP
Utilisation optimale et professionnelle de PHPUtilisation optimale et professionnelle de PHP
Utilisation optimale et professionnelle de PHP
 
PHP5 - POO
PHP5 - POOPHP5 - POO
PHP5 - POO
 
Epitech securite-2012.key
Epitech securite-2012.keyEpitech securite-2012.key
Epitech securite-2012.key
 
Cours php & Mysql - 3éme partie
Cours php & Mysql - 3éme partieCours php & Mysql - 3éme partie
Cours php & Mysql - 3éme partie
 
Nouveautés php 7
Nouveautés php 7Nouveautés php 7
Nouveautés php 7
 
PHP 7 et Symfony 3
PHP 7 et Symfony 3PHP 7 et Symfony 3
PHP 7 et Symfony 3
 
Php & My Sql
Php & My SqlPhp & My Sql
Php & My Sql
 
Formation PHP
Formation PHPFormation PHP
Formation PHP
 
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINEIntroduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
Introduction au langage PHP (1ere partie) élaborée par Marouan OMEZZINE
 
Solution Linux 2009 - JavaScript
Solution Linux 2009 - JavaScriptSolution Linux 2009 - JavaScript
Solution Linux 2009 - JavaScript
 
Qui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le codeQui a laissé son mot de passe dans le code
Qui a laissé son mot de passe dans le code
 

Similar a De legacy à symfony

Les bonnes pratiques de l'architecture en général
Les bonnes pratiques de l'architecture en généralLes bonnes pratiques de l'architecture en général
Les bonnes pratiques de l'architecture en général
Geoffrey Bachelet
 
Soutenance Zend Framework vs Symfony
Soutenance Zend Framework vs SymfonySoutenance Zend Framework vs Symfony
Soutenance Zend Framework vs Symfony
Vincent Composieux
 
démonstration code source site web ecole.docx
démonstration code source site web ecole.docxdémonstration code source site web ecole.docx
démonstration code source site web ecole.docx
VincentBweka
 
Qualité logicielle
Qualité logicielleQualité logicielle
Qualité logicielle
cyrilgandon
 
Patterns and OOP in PHP
Patterns and OOP in PHPPatterns and OOP in PHP
Patterns and OOP in PHP
julien pauli
 

Similar a De legacy à symfony (20)

De Legacy à Symfony
De Legacy à SymfonyDe Legacy à Symfony
De Legacy à Symfony
 
Les bonnes pratiques de l'architecture en général
Les bonnes pratiques de l'architecture en généralLes bonnes pratiques de l'architecture en général
Les bonnes pratiques de l'architecture en général
 
Php1
Php1Php1
Php1
 
"Un module Prestashop, comment ca marche?"
"Un module Prestashop, comment ca marche?""Un module Prestashop, comment ca marche?"
"Un module Prestashop, comment ca marche?"
 
Quelle place pour le framework Rails dans le développement d'application web
Quelle place pour le framework Rails dans le développement d'application webQuelle place pour le framework Rails dans le développement d'application web
Quelle place pour le framework Rails dans le développement d'application web
 
Soutenance Zend Framework vs Symfony
Soutenance Zend Framework vs SymfonySoutenance Zend Framework vs Symfony
Soutenance Zend Framework vs Symfony
 
Java 9 modulo les modules devoxx fr 2017
Java 9 modulo les modules devoxx fr 2017Java 9 modulo les modules devoxx fr 2017
Java 9 modulo les modules devoxx fr 2017
 
Les principes de base de PHP
 Les principes de base de PHP  Les principes de base de PHP
Les principes de base de PHP
 
Php cours
Php coursPhp cours
Php cours
 
démonstration code source site web ecole.docx
démonstration code source site web ecole.docxdémonstration code source site web ecole.docx
démonstration code source site web ecole.docx
 
Open close principle, on a dit étendre, pas extends !
Open close principle, on a dit étendre, pas extends !Open close principle, on a dit étendre, pas extends !
Open close principle, on a dit étendre, pas extends !
 
Qualité logicielle
Qualité logicielleQualité logicielle
Qualité logicielle
 
Patterns and OOP in PHP
Patterns and OOP in PHPPatterns and OOP in PHP
Patterns and OOP in PHP
 
Synchroniser ses applis simplement avec akeneo/batch
Synchroniser ses applis simplement avec akeneo/batchSynchroniser ses applis simplement avec akeneo/batch
Synchroniser ses applis simplement avec akeneo/batch
 
20111006 bonnes pratiques-gi_g_v1
20111006 bonnes pratiques-gi_g_v120111006 bonnes pratiques-gi_g_v1
20111006 bonnes pratiques-gi_g_v1
 
Retour d'expérience sur PowerShell
Retour d'expérience sur PowerShellRetour d'expérience sur PowerShell
Retour d'expérience sur PowerShell
 
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
 
Comment écrire du code testable ?
Comment écrire du code testable ?Comment écrire du code testable ?
Comment écrire du code testable ?
 
Des tests modernes pour Drupal
Des tests modernes pour DrupalDes tests modernes pour Drupal
Des tests modernes pour Drupal
 
Gitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement ContinueGitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement Continue
 

De legacy à symfony

  • 1. DE LEGACY À SYMFONY SYMFONY MONTRÉAL 16 JUIN 2016 Etienne Lachance @elachance
  • 2. QUI SUIS-JE Sys admin de formation Programmeur depuis ~10 ans Propriétaire de elcweb.ca Consultation en entreprise Programmation Hébergement spécialisé Fin de l'auto promotion
  • 3. CONTEXT Evolution d'un projet "Legacy" sans affecter la productivité mais en introduisant les bonne pratique d’un nouveau framework.
  • 4. PAR LEGACY J'ENTEND peu/pas de documentation peu/pas de tests Code procédural Code spaghetti Duplication de code (copier-coller) Include-ception Couplage de responsabilité ... Non SOLID
  • 5. OBJECTIF Permet de refactoriser le code petit peu par petit peu mais d'avoir des avantages rapidement.
  • 6. ATTENTION! IL EST FORTEMENT RECOMMANDÉ D’ÉCRIRE DES TESTS AUTOMATISÉS. PHPUnit Behat
  • 8. 1. PARALLEL simple a implémenter (mod_rewrite) aucune communication direct entre les 2 applications utilisation de la BD ou Redis pour l'échange entre les 2 apps peu/pas d'impacte sur l'application 1
  • 9. 2. PROXY l'utilisateur voie uniquement une application (Symfony) necessite plus de travail pour la mise en place "wrapper" pour les requêtes a l'application Legacy authenti cation sécurité entre les 2 applications peu/pas d'impacte sur l'application Legacy
  • 10. 3. INTÉGRATION On veut changer la structure fondamental du code actuel. une seule application OPTION PRÉSENTÉ
  • 11. QU'EST-CE QUE SYMFONY? Symfony est * une collection de composante un framework applicatif une philosophy une communauté Symfony est a la base un framework HTTP GESTION DES REQUÊTE / RÉPONSE source: http://symfony.com/what-is-symfony
  • 13. STRUCTURE DE FICHIER BONNE PRATIQUE: PLACER LE CODE A L'EXTERIEUR DU RÉPERTOIRE PUBLIQUE.
  • 16. EXEMPLE DE TYPE "INCLUDE-CEPTION" <?php // index.php include("includes/common.php"); include("includes/config.php"); $mod = $_GET['mod']; if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui',$mod)) $mod = "dashboard"; include ("modules/".$mod.".php"); aucun namespace logique basé sur include() / require()
  • 17. LEGACY CONTROLLER namespace AppBundleController; use ...; class LegacyController extends Controller { /** @Route("/index.php") */ public function legacyAction() { // __DIR__ == 'src/AppBundle/Controller' include __DIR__ . '/../includes/common.php'; include __DIR__ . '/../includes/config.php'; // @todo: renommé $mod pour $module $mod = $_GET['mod']; if ($mod=="" || !preg_match('/^[A-Za-z1-90_]+$/Ui', $mod)) { $mod = "dashboard"; }
  • 18. LEGACY CONTROLLER - SUITE // Include module file $filename = __DIR__ . '/../modules/' . $mod . '.php'; if (!file_exists($filename)) { throw new NotFoundHttpException('Module ' . $mod . ' not found'); } // ob_start: Turn on output buffering ob_start(); include $filename; return new Response(ob_get_clean()); } }
  • 19. RÉCAPITULATION déplacer les chiers a l'extérieur du répertoire publique véri er si le module exist si le module n'existe pas, retourne une erreur 404 encapsuler les "echo" du code legacy dans un objet Response
  • 20. PROCHAINE ÉTAPES Authenti cation/Autorisation (incluant la session) Isolation de la base de donnée (Repository) Vue (Templates)
  • 23. UTILISATEUR Implement UserInterface <?php namespace AppBundleSecurityUser; use ... class User implements UserInterface { private $username; private $password; private $salt prirate $roles; ... }
  • 24. PROVIDER Est responsable d'aller chercher l'utilisateur Implement UserProviderInterface namespace AppBundleSecurityUser; use ... class UserProvider implements UserProviderInterface { public function loadUserByUsername($username) { ... } public function refreshUser(UserInterface $user) { ... } public function supportsClass($class) { return $class === 'AppBundleSecurityUserUser'; } }
  • 25. ENCODER Responsable d'encoder et de valider un mot de passe namespace AppBundleSecurityEncoder; use SymfonyComponentSecurityCoreEncoderBasePasswordEncoder; class LegacyMd5Encoder extends BasePasswordEncoder { public function isPasswordValid($encoded, $raw, $salt = null) :bool { return $this->comparePasswords(strtolower($encoded), strtolower($this->encodePassword($raw, $ } public function encodePassword($raw, $salt = null) :string { return md5($raw . 'secret_global_a_l_application'); } }
  • 26. CONFIGURATION app/con g/services.yml services: app.user_provider: class: AppBundleSecurityUserUserProvider app.security.encoder.md5: class: AppBundleSecurityEncoderLegacyMd5Encoder app/con g/security.yml security: encoders: AppBundleSecurityUserUser: id: app.security.encoder.md5 providers: legacy: id: app.user_provider firewall: main: pattern: ^/ http_basic: ~
  • 27. RECOMMANDATION Utilisation d'un algorithme plus sécuritaire comme bcrypt ou sha512 Convertion des mots de passes "on the y"
  • 28. DOCTRINE Permet de représenter en Objet et non en tables.
  • 29. DOCTRINE / REPOSITORY Dans le contexte de Doctrine, un Repository est utilisé pour allez chercher l'information Centraliser les requêtes SQL Isoler les requêtes du "controlleur" Classi er par context d'objet (Utilisateur/Produit/Client)
  • 30. LEGACY <?php // ... $conn = mysql_connect($db_host, $db_user, $db_password); if (!$conn) { echo "Unable to connect to DB: " . mysql_error(); exit; } if (!mysql_select_db($dbname)) { echo "Unable to select mydbname: " . mysql_error(); exit; } $sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($_GET['cat']).";"
  • 31. LEGACY - SUITE $result = mysql_query($sql); if (!$result) { echo "Could not successfully run query ($sql) from DB: " . mysql_error(); exit; } if (mysql_num_rows($result) == 0) { echo "No rows found, nothing to print so am exiting"; exit; } $data = array(); while ($row = mysql_fetch_assoc($result)) { $data = $row; } mysql_free_result($result);
  • 32. GÉNÉRATION D'ENTITÉ A PARTIR D'UNE BASE DE DONNÉ EXISTANTE $ php bin/console doctrine:mapping:import --force AcmeBlogBundle xml $ php bin/console doctrine:mapping:convert annotation ./src $ php bin/console doctrine:generate:entities AcmeBlogBundle http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
  • 33. REPOSITORY namespace AppBundleEntity; use DoctrineORMEntityRepository; class ProductRepository extends EntityRepository { public function findByCategory($categoryId) { $sql = "SELECT * FROM products WHERE category = ".mysql_real_escape_string($categoryId $stmt = $this->getEntityManager()->getConnection()->prepare($sql); $stmt->execute(); return $stmt->findAll(); } } RawSQLTrait: https://gist.github.com/estheban/3eae41271f6cf5f3180a
  • 34. UTILISATION DANS UN CONTROLLEUR class ProductController extends Controller { /** * @Route("/product.php/category/{id}") */ public function productByCategory(Category $category) { // throw 404 si pas de Catégorie trouvée $entityManager = $this->getDoctrine()->getManager(); return $entityManager ->getRepository("AppBundle:Product") ->findByCategory($category->getId()); } } Voir: @ParamConverter
  • 35. TEMPLATES Ne pas convertir les templates pour le "Fun" On peut retourner: une réponse directement (aucun template) un template en php un template en twig (natif) un template en smarty un template en ...
  • 36. EXEMPLE D'UN CONTROLLEUR QUI RETOURNE UNE RÉPONSE BASÉ SUR UN TEMPLATE public function indexAction() { // some logic to retrieve the blogs $blogs = ...; $this->render( 'AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs) ); }
  • 37. EXEMPLE D'UN TEMPLATE EN TWIG {# app/Resources/views/blog/index.html.twig #} {% extends 'blog/layout.html.twig' %} {% block content %} {% for blogPost in blogs %} <h2>{{ blogPost.title }}</h2> <p>{{ blogPost.body }}</p> {% endfor %} {% endblock %}
  • 38. EXEMPLE DE SMARTY ET TWIG protected function renderSmarty($template, $outputData) { $smarty = new SmartyEngine(); $smarty->assign($outputData); return $this->render( '@ElcwebLegacy/Default/default.html.twig', ['output' => $smarty->fetch($template)] ); } public function bobAction() { $outputData = ['foo' => 'bar']; return $this->renderSmarty('bob.tpl', $outputData); }