Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
CursoSymfony Unizar.MiguelMartn2012.
INDICE
Da1:TEORA
PRCTICA MVC+FundamentosdeSymfony +Routing (bsico) InstalacindeWAMP2.2+Symfony CONTROLLER+Routing (avanzado) EjerciciosCONTROLLERyROUTING TEMPLATING(TWIG) EjerciciosTEMPLATING Formularios EjerciciosFormularios Doctrine Crearaplicacinparagestionartareas
Da2:TEORA
PRCTICA
Da3:TEORA
PRCTICA
Da4:TEORA
PRCTICA
Da5:TEORA
PRCTICA
BOLAEXTRA:Seguridad(autenticacinyautorizacin)
2
Da1:IntroduccinaSymfony yMVC
Modelovistacontrolador(MVC):conceptoyejemplo FundamentosdeSymfony MecanismoHTTP:leerRequest,generarResponse LecturadeRequest/GeneracindeResponse enSymfony Controlador:paraqu sirve? AlternativasaSymfony InstalacindeSymfony Consejosdedesarrollo(+instalacindesoftware) Introduccinalworkflow deSymfony Creacindelprimerproyecto Entornos
IntroduccinaModeloVistaControlador(MVC)
[WIKIPEDIAfor Modelo_Vista_Controlador: http://es.wikipedia.org/wiki/Modelo_Vista_Controlador ]
Modelo:Estaeslarepresentacinespecfica delainformacinconlacualelsistemaopera. Enresumen,elmodeloselimitaalorelativo delavista ysucontrolador facilitandolas presentacionesvisualescomplejas.Elsistema tambinpuedeoperarconmsdatosno relativosalapresentacin,haciendouso integradodeotraslgicasdenegocioyde datosafinesconelsistemamodelado. Vista:Estepresentaelmodeloenunformato adecuadoparainteractuar,usualmentela interfazdeusuario. Controlador:Esterespondeaeventos, usualmenteaccionesdelusuario,einvoca peticionesalmodeloy,probablemente,ala vista.
CursoSymfony Unizar.MiguelMartn2012.
IntroduccinaModeloVistaControlador(MVC)
AplicacinenPHPbsico:
ConexinaBD Recuperacindedatos Muestradatos
<?php //index.php $link =mysql_connect('localhost','myuser','mypassword'); mysql_select_db('blog_db',$link); $result =mysql_query('SELECTid,title FROMpost',$link); ?> <html> <head> <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php while ($row =mysql_fetch_assoc($result)):?> <li> <ahref="/show.php?id=<?php echo$row['id']?>"> <?php echo $row['title']?> </a> </li> <?php endwhile;?> </ul> </body> </html> <?php mysql_close($link);?>
MezclaPHP+HTML(lgicay presentacin) Rpidadeescribir Singestindeerrores Difcilreutilizacindelcdigo Difcildemantener Noseaisla delSGBD(ysi cambiamosaPostgre enunfuturo?)
IntroduccinaMVC(II)
Separacin:
<?php //index.php $link =mysql_connect('localhost','myuser','mypassword'); mysql_select_db('blog_db',$link); $result =mysql_query('SELECTid,title FROMpost',$link);
Lgica(controlador)
$posts =array(); while ($row =mysql_fetch_assoc($result)){ $posts[]=$row; } mysql_close($link); //include the HTMLpresentation code require 'templates/list.php'; <html> <head> <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php foreach ($posts as $post):?> <li> <ahref="/read?id=<?php echo$post['id']?>"> <?php echo $post['title']?> </a> </li> <?php endforeach;?> </ul> </body> </html> 6
Presentacin
IntroduccinaMVC(III)
Qu pasasiqueremosreutilizarel cdigodeconexinalaBD? YsicambiamoselsistemaGestorde laBD?
<?php //model.php function open_database_connection(){ $link =mysql_connect('localhost','myuser','mypassword'); mysql_select_db('blog_db',$link); return $link; } function close_database_connection($link){ mysql_close($link);} function get_all_posts(){ $link =open_database_connection(); $result =mysql_query('SELECTid,title FROMpost',$link); $posts =array(); while ($row =mysql_fetch_assoc($result)){ $posts[]=$row; } close_database_connection($link); return $posts; }
Solucin:MODELO
Accesoadatos
<?php //index.php
Ahoraindex.php esmuysimple:
recogerlosdatosdelmodelo,realizarla lgicadeseadaconellos(controlador)y pasrselosalacapadepresentacin(vista).
IntroduccinaMVC(IV)
Qu pasasiqueremosreutilizar lavista? Creamosunnuevofichero: layout.php ynuestrotemplate (templates/list.php)se simplifica. Laideaesalmacenarporunlado losvaloresdelasvariables (list.php)yporotro, mostrarlos(layout.php)
<! templates/layout.php > <html> <head> <title><?php echo $title ?></title> </head> <body> <?php echo $content ?> </body> </html>
<?php ob_start()?> <h1>List of Posts</h1> ob_start() iniciala"captura"delbufferde <ul> <?php foreach ($posts as $post):?> salidayob_end_flush() terminala <li> capturadelbufferdesalida.Deestemodo <ahref="/read?id=<?php echo$post['id']?>"> podemoshacerunechodetodoesebuffer. <?php echo $post['title']?> </a> </li> <?php endforeach;?> </ul> <?php $content =ob_get_clean()?> <?php include 'layout.php'?>
IntroduccinaMVC(V)
Vamosavercmocrearamosunavista nueva,queenvezdemostrarellistadode posts,muestreunoenconcreto.
//Aadimosestafuncinamodel.php function get_post_by_id($id){ $link =open_database_connection(); $id =mysql_real_escape_string($id); $query ='SELECTdate,title,bodyFROMpostWHEREid ='.$id; $result =mysql_query($query); $row =mysql_fetch_assoc($result); close_database_connection($link); return $row; } <?php //show.php require_once 'model.php'; $post =get_post_by_id($_GET['id']); require 'templates/show.php'; <?php //templates/show.php $title =$post['title']?> <?php ob_start()?> <h1><?php echo $post['title']?></h1> <div class="date"><?php echo $post['date']?></div> <div class="body"> <?php echo $post['body']?> </div> <?php $content =ob_get_clean()?> <?php include 'layout.php'?>
Creamoselnuevocontrolador (show.php)
Creamoslavista (templates/show.php)
IntroduccinaMVC(VI)
Seintroduceunnuevoproblema:tenemosVARIOSCONTROLADORES(index.php yshow.php).
<?php //index.php require_once 'model.php'; $posts =get_all_posts(); require 'templates/list.php'; <?php //show.php require_once 'model.php'; $post =get_post_by_id($_GET['id']); require 'templates/show.php';
Solucin:Controladornicoqueejecutatodaslaspeticiones: SinFRONTCONTROLLER /index.php =>Listadodeposts delblog(ejecutaindex.php) /show.php =>Unpostconcreto(ejecutashow.php) ConFRONTCONTROLLER(index.php) /index.php =>Listadodeposts delblog(ejecutaindex.php) /index.php/show =>Unpostconcreto(ejecutaindex.php)
10
IntroduccinaMVC(VII)
<?php //index.php (seconvierteenelcontroladorprincipal) //cargamoselmodelo require_once 'model.php'; //cargamoselficherodondesedefinenlasfuncionesdelosanteriorescontroladores... require_once 'controllers.php'; //ejecutamosunafuncinuotraenfuncinalarutainvocada... $uri =$_SERVER['REQUEST_URI']; if ($uri =='/index.php'){//sehainvocadoaindex.php >debemoslistartodoslospost list_action(); }elseif ($uri =='/index.php/show'&&isset($_GET['id'])){ //sehainvocadoa/index.php/show>debemosmostrarelpostidentificadopor$_GET[id] show_action($_GET['id']); }else { header('Status:404Not Found'); echo '<html><body><h1>Page Not Found</h1></body></html>'; } //controllers.php (listado detodas las posibles rutas >acciones alas que invocar) function list_action() { $posts =get_all_posts(); require 'templates/list.php'; } function show_action($id) { $post =get_post_by_id($id); require 'templates/show.php'; }
11
IntroduccinaMVC(VIII)
index.php :controlalasrutasyqu funcin(decontrollers.php)ejecutarencadacaso controllers.php:contienelasaccionesdecadacontrolador(list_action()yshow_action model.php:contienelasfuncionesdeaccesoadatos(BD) PHPconMVC layout.php:contienelaplantillageneraldelavista:generalasalidaapartirdeunaestructura
HTMLestticoylosvaloresdealgunasvariablesquesonasignadosen:
PHPinicial
index.php:hacetodo
12
FundamentosSYMFONY
Desarrollarmsrpido:
*mejororganizacin *funcionalidadesmscomunes
Construiraplicaciones:
*msrobustas *msflexibles *msseguras
CursoSymfony Unizar.MiguelMartn2012.
13
FUNDAMENTOSSYMFONY(II)
Resumen:misindecualquieraplicacinweb =Interpretarpeticinydevolverrespuesta. Suelehabermuchasfuncionalidadescomunes entretodaslasapps web:consultasalabasededatos,
renderizadoyreutilizacindeplantillas,envodecorreos,validacindeentradasdeusuario,controldelaseguridadweb.
CursoSymfony Unizar.MiguelMartn2012.
14
Mecanismo bsicoHTTP
PHPcreavariablessuperglobales conainformacindelapeticin(request)
<?php $uri =$_SERVER['REQUEST_URI']; $foo =$_GET['foo']; header('Contenttype:text/html'); echo 'TheURIrequestedis:'.$uri;echo 'Thevalueofthe "foo"parameteris:'.$foo; ?>
Trasinterpretarelcdigo,devuelvelarespuesta
HTTP/1.1200OK Date:Sat,03Apr201102:14:33GMTServer:Apache/2.2.17(Unix) ContentType:text/html TheURIrequestedis:/testing?foo=symfony Thevalueofthe "foo"parameteris:symfony
15
SYMFONY
claseRequest
Clasecuyosobjetosalmacenaninformacindelapeticin (Request)
Proveemtodos(comoisSecure()oisXmlHttpRequest())querealizanalgunastareas engorrosasdetenerquehacerlasenPHP.
http://symfony.com/doc/current/components/http_foundation.html#accessingrequestdata
<?php use Symfony\Component\HttpFoundation\Request; $request =Request::createFromGlobals(); //laURLsolicitada (p.ej./about)SINPARAMETROS $request>getPathInfo(); //Acceder alas variablesGETyPOST $request>query>get('foo'); $request>request>get('bar','valorpor defecto si barnoexiste'); //Acceder alas variablesdel SERVER $request>server>get('HTTP_HOST'); //Acceso alainstancia deun UploadedFile identificado por foo $request>files>get('foo'); //Acceso alas cookies... $request>cookies>get('PHPSESSID'); //Acceso ainformacin delas cabeceras del Request,ya enminsculas ynormalizadas $request>headers>get('host'); $request>headers>get('content_type'); $request>getMethod();//GET,POST,PUT,DELETE,HEAD $request>getLanguages();//arraydelenguajes que acepta el cliente
16
SYMFONY
claseResponse
Clasecuyosobjetossirvenparagenerarlarespuestaalcliente.
http://symfony.com/doc/current/components/http_foundation.html#response
<?php use Symfony\Component\HttpFoundation\Response; // construccinamano delaResponse $response =new Response(); $response>setContent('<html><body><h1>Hello world!</h1></body></html>'); $response>setStatusCode(200); $response>headers>set('ContentType','text/html'); $response>setCharset('ISO88591');//pordefecto,asumeUTF8 //envodecabecerasHTTP+contenido $response>send();
Yatenemosclarocmoprocesarlapeticinydevolverlarespuesta.Lapartejugosa consisteenescribirelcdigoqueinterpretalapeticinycrealarespuesta.
Qu msofreceSymfony?
Loveremospronto
17
SYMFONYFront controller
PHPTradicional EnPHPcadaficherorecogelapeticin,realiza lalgicacorrespondienteydevuelveuna respuesta:
index.php blog.php contact.php
18
3. Devolverrespuestaalcliente
CursoSymfony Unizar.MiguelMartn2012. 19
SYMFONYAlternativas
COMPARATIVA http://www.phpframeworks.com/
CursoSymfony Unizar.MiguelMartn2012.
20
InstalacindeSymfony
DescargarSymfony StandardEdition:enZIP! http://symfony.com/download
www/< your web root directory Symfony/< the unpacked archive app/ cache/ config/ logs/ Resources/ bin/ src/ Acme/ DemoBundle/ Controller/ Resources/ ... vendor/ symfony/ doctrine/ ... web/ app.php ...
Configuracindelentorno: http://localhost/Symfony/web/config.php
21
22
23
ConfiguracindeSymfony:seguirlospasos
Vamosadejareluser root delaBDsinpwd,algoqueNUNCAdeberahacersefueradeuna demo
24
Aadirbinariodephp alavariablePATH
vamos a aadir la carpeta de los binarios de php a la variable PATHde Windows.
c:\wamp2.2\bin\php\php5.3.8\php.exe),
; c:\wamp2.2\bin\php\php5.3.8
25
Responsabilidaddelprogramador=
Escribirelcdigoquemapealapeticindelcliente (/demo/hello/Miguel)alrecursoasociadoconella (lapginaquemuestraHello Miguel! enHTML)
26
PrimerprogramaenSymfony:ROUTING
http://localhost/Symfony/web/app_dev.php/demo/hello/Miguel
app/config/routing.yml archivodeconfiguracinconreglas app/config/routing_dev.yml archivodeconfiguracinconreglasdelentornodedesarrollo(app_**dev**.php) enelcontrolador
*MssobrearchivosYAML(.yml)enhttp://www.yaml.org/
Nombrelgicodelcontrolador:referenciaelmtodoindexAction de Acme\DemoBundle\Controller\WelcomeController
_welcome: pattern:/ defaults:{_controller:AcmeDemoBundle:Welcome:index } _demo: resource:"@AcmeDemoBundle/Controller/DemoController.php" type:annotation prefix:/demo //src/Acme/DemoBundle/Controller/WelcomeController.php namespace Acme\DemoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class WelcomeController extends Controller { public function indexAction() { //sevaarenderizareltemplate AcmeDemoBundle:Welcome:index.html.twig return $this>render('AcmeDemoBundle:Welcome:index.html.twig'); } } TWIG(motordetemplates pordefecto).Msenhttp://twig.sensiolabs.org/ HacereferenciaalarchivoResources/views/Welcome/index.html.twig dentrodesrc/Acme/DemoBundle 27
PrimerprogramaenSymfony:CONTROLLER
http://localhost/Symfony/web/app_dev.php/demo/hello/Miguel
app/config/routing.yml archivodeconfiguracinconreglas app/config/routing_dev.yml archivodeconfiguracinconreglasdelentornodedesarrollo(app_**dev**.php) enelcontrolador
*MssobrearchivosYAML(.yml)enhttp://www.yaml.org/
class DemoController extends Controller { /** *@Route("/hello/{name}",name="_demo_hello") *@Template() */ public function helloAction($name) { return array('name'=>$name); } //... } 28
EjerciciosSYMFONY
29
EjerciciosSYMFONY
Observarenelfrontcontroller (app/config/routing_dev.yml)qu controlador seest ejecutandoparaesaURL: _demo: resource:"@AcmeDemoBundle/Controller/DemoController.php" type:annotation prefix:/demo Abrirsrc/Acme/DemoBundle/Controller/DemoController.php y observar el cdigo.Aqui sehace laLGICA(recuerda:el controlador debe generar laRESPUESTA)yvemos las rutas como notas precedidas por @Route ({name}es un placeholder) /** *@Route("/hello/{name}",name="_demo_hello") *@Template() */ publicfunctionhelloAction($name) { returnarray('name'=>$name); }
IndicaquelasURLs seindicarncomo anotacionesenelficheroDemoController. Todaslasrutasquecomiencencon/demolas resolver elcontroladorDemoController.php
Enelejemploanterior,elcontroladorllamabaa
$this>render('AcmeDemoBundle:Welcome:index.html.twig');
Abrirsrc/Acme/DemoBundle/Resources/views/Demo/hello.html.twig ymodificarlaPLANTILLA: {%extends "AcmeDemoBundle::layout.html.twig"%} {%block title ¡Hola "~name %} {%block content %} <h1>¡Hola {{name }}!</h1> {%endblock %} {%setcode =code(_self)%} 30
EjerciciosSYMFONY
E2.Modificar el ejemplo para que enlugar deponer Hola x! ponga Hola X! para /demo/hello/x y/demo/hello/X
31
EjerciciosSYMFONY
S2.Modificar el ejemplo para que enlugar deponer Hola x! ponga Hola X! para /demo/hello/xy/demo/hello/X
Observarenelfrontcontroller (app/config/routing_dev.yml)qu controlador seest ejecutandoparaesaURL: _demo: resource:"@AcmeDemoBundle/Controller/DemoController.php" type:annotation prefix:/demo Abrirsrc/Acme/DemoBundle/Controller/DemoController.php y observar el cdigo.Aqui sehace laLGICA(recuerda:el controlador debe generar laRESPUESTA)yvemos las rutas como notas precedidas por @Route yque {name}es un placeholder /** *@Route("/hello/{name}",name="_demo_hello") *@Template() */ publicfunctionhelloAction($name) { returnarray('name'=>strtoupper($name)); }
Enestecaso,podremosrealizarlamodificacinobienen elControlador(ypasarlavariableyaenmaysculasala Vista): strtoupper($name)
Eltemplate src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig {%extends "AcmeDemoBundle::layout.html.twig"%} {%block title ¡Hola "~name %} {%block content %} <h1>¡Hola {{name|upper }}!</h1> {%endblock %} {%setcode =code(_self)%} 32
EjerciciosSYMFONY
EjerciciosSYMFONY
Observarenelfrontcontroller (app/config/routing_dev.yml)qu controlador seest ejecutandoparaesaURL: _demo: resource:"@AcmeDemoBundle/Controller/DemoController.php" type:annotation prefix:/demo Modificarsrc/Acme/DemoBundle/Controller/DemoController.php Para aadir lanueva ruta: /** *@Route("/hello/}",name="_demo_hello") *@Template() */ publicfunctionnonameAction($name) { returnarray('name'=>strtoupper($name)); }
Definimosunanuevaruta(/hello/)yhacemosquese invoquelafuncinnonameAction cuandosellameaesta URL.
Copiamostemplate src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig a src/Acme/DemoBundle/Resources/views/Demo/noname.html.twig yrealizamoslasmodificaciones pertinentes {%extends "AcmeDemoBundle::layout.html.twig"%} {%block title ¡Hola DESCONOCIDO!"%} {%block content %} <h1>¡Hola.Noteconozco.</h1> {%endblock %} {%setcode =code(_self)%} 34
EjerciciosSYMFONY
Observarenelfrontcontroller (app/config/routing_dev.yml)qu controlador seest ejecutandoparaesaURL: _demo: resource:"@AcmeDemoBundle/Controller/DemoController.php" type:annotation prefix:/demo Modificarsrc/Acme/DemoBundle/Controller/DemoController.php Para aadir lanueva ruta: /** *@Route("/hello/}",name="_demo_hello") *@Template(AcmeDemoBundle:Demo:hello.html.twig) */ publicfunctionhelloAction() { $name =ANONIMO; returnarray('name'=>$name); }
Definimosunanuevaruta/hello/yhacemosquese renderice eltemplate hello.html.twig (@Template(AcmeDemoBundle:Demo:hello.html.twig)) metindolenosotroselvalorde$name deformamanual.
Dejamostalcualsrc/Acme/DemoBundle/Resources/views/Demo/hello.html.twig
35
DESARROLLO:
CONSEJOS(parasermsfeliz)
OPTIMIZACIN:
Puedes usar HttpWatch para Firefox para visualizar y optimizar la carga de tus recursos (comprimiendo CSS, JS, optimizando imgenes, creando CSS Sprites, etc) http://www.httpwatch.com/
E5:Creacindeunnuevoproyecto
Welcometothe Symfony2bundlegenerator ... Use/insteadof\ forthe namespacedelimitertoavoidanyproblem. Bundlenamespace[curso/e5Bundle]: Inyourcode,abundleisoftenreferencedbyitsname.Itcanbethe concatenationofallnamespacepartsbutit'sreallyuptoyoutocome upwithauniquename(agoodpracticeistostartwiththe vendorname). Basedonthe namespace,wesuggestcursoe5Bundle. Bundlename[cursoe5Bundle]: Thebundlecanbegeneratedanywhere.Thesuggesteddefaultdirectoryuses the standardconventions. Targetdirectory[W:\WEB\wamp2.2\www\Symfony/src]: Determinethe formattouseforthe generatedconfiguration. Configurationformat(yml,xml,php,orannotation)[yml]: Tohelpyougettingstartedfaster,the commandcangeneratesome codesnippetsforyou. Doyouwanttogeneratethe wholedirectorystructure[no]? Summarybeforegeneration
cd c:\wamp2.2\www\Symfony
//Aadeautomticamenteaapp/AppKernel.php public function registerBundles() { $bundles =array( //... new Acme\HelloBundle\AcmeHelloBundle(), ); //... return $bundles;
Youaregoingtogeneratea"curso\e5Bundle\cursoe5Bundle"bundle in"W:\WEB\wamp2.2\www\Symfony/src/"usingthe "yml"format. Doyouconfirmgeneration[yes]? Bundlegeneration Generatingthe bundlecode:OK Checkingthatthe bundleisautoloaded:OK ConfirmautomaticupdateofyourKernel[yes]? Enablingthe bundleinsidethe Kernel:OK Confirmautomaticupdateofthe Routing[yes]? Importingthe bundleroutingresource:OK
Youcannowstartusingthe generatedcode!
37
PrimerejercicioenSymfony
E51:Pginadebienvenida
E51S1:Lasolucinmssimple:IncluirResponse enelcontroladorydevolverdirectamentelaRespuesta(sin templates nivariables)
#this is app/config/routing.yml cursoe5Bundle: resource:"@cursoe5Bundle/Resources/config/routing.yml" prefix:/curso #this is src/curso/e5Bundle/Resources/config/routing.yml cursoe5Bundle_homepage: pattern:/ defaults:{_controller:cursoe5Bundle:Default:index } <?php //this is src\Curso\e5Bundle\Controller\DefaultController.php namespace curso\e5Bundle\Controller; useSymfony\Bundle\FrameworkBundle\Controller\Controller; useSymfony\Component\HttpFoundation\Response; class DefaultController extends Controller { public function indexAction(){ return new Response('<html><head><title>CursodeSymfony</title></head> <body>Bienvenidoalcurso</body></html>'); } }
39
E51:Pginadebienvenida
E51S2:Contemplates yvariables:reutilizacinyflexibilidad(facilidaddemodificacin).Losficherosderutasse mantienenigual.SemodificaelDefaultController yeltemplate arenderizar(index.html.twig)
#this is app/config/routing.yml cursoe5Bundle: resource:"@cursoe5Bundle/Resources/config/routing.yml" prefix:/curso
<?php //this is src\Curso\e5Bundle\Controller\DefaultController.php namespace curso\e5Bundle\Controller; useSymfony\Bundle\FrameworkBundle\Controller\Controller; useSymfony\Component\HttpFoundation\Response; class DefaultController extends Controller NOMBRES DE LAS PLANTILLAS { <nombreBundle>:<nombreController>:<nombrePlantilla> public function indexAction(){ $titulo=CursodeSymfony"; $msg ="Bienvenidoalcurso"; return $this>render('cursoe5Bundle:Default:index.html.twig',array('titulo'=>titulo,'mensaje'=>$msg )); } }
<! customized bysrc\curso\e5Bundle\Resources\views\Default\index.html.twig > <html> <head> <title>{{titulo}}</title> </head> <body> {{mensaje}} </body> </html>
40
Entornos
Symfony ofrecedistintosENTORNOS.Laejecucindelcdigoesidntica, perosepermitendiferentesconfiguraciones.
Porejemplo,enentornodedesarrollo(dev)seloggean warnings yerrores,mientrasqueenentornodeproduccin(prod)seloggean nicamenteerrores. Tambinexistendiferenciasdecacheo(enentornodeproduccinsecacheayenentornodedesarrolloloscambiossevanreflejando)
[1]http://httpd.apache.org/docs/current/mod/mod_rewrite.html
41
Yaconocemoslobsico
ROUTING Controlderutas
Motivacin Ficherosderouting (fich principaleinclusindeficherosexternos) Rutasbsicasconplaceholders Rutasbsicasconplaceholders (defaults) Rutasbsicasconplaceholders (defaults)yrequerimientos Rutasavanzadas Parmetrosespeciales(_controller,_format,_locale) Visualizacinydebugging derutas GeneracindeURLs
43
ROUTING:motivacin
Cadadasehacemsimportanteconstruiraplicacionesweb conrutasSEF(searchenginefriendly),que: seanfcilmentelegibles(yrecordables), contenganlaspalabrasclaveparalasquequeremosposicionarnos mantenganciertajerarquizacin,etc. seanflexibles(puedacambiarlasconmuchafacilidad,sinrecorrertodoslosarchivosquelasenlazan)[1] gestindeerrores(404)yverificacindeparmetrosderutas(seguridad!) sitiosmultiidioma
No: www.mitienda.com/moduleprods/index.php?product_id=1238s81KZZ&show=detail&foo=bar&sessid=9182757ka1
ROUTING:controlderutas
Comoyaadelantamoselda1,Symfony ofreceunmecanismodecontrolderutasmediante (1)Ficherogeneralderutas:puedetenerdefinidaslasrutasdirectamenteoincluirotrosficherosderutas
#Symfony/app/config/routing.yml proyecto1Bundle: resource:@AcmeHelloBundle/Resources/config/routing.yml"
#src/Acme/HelloBundle/Resources/config/routing.yml acme_hello: pattern:/hello/{name} defaults:{_controller:AcmeHelloBundle:Hello:index }
[opcional](2)Ficheroderutasparaelproyectoqueestamosdesarrollando.
#Symfony/src/curso/e5Bundle/Resources/config/routing.yml cursoe5Bundle_homepage: pattern:/ defaults:{_controller:cursoe5Bundle:Default:index} cursoe5Bundle_flashmessage: pattern:/flashmessage defaults:{_controller:cursoe5Bundle:Default:flashmessage}
45
ROUTING:placeholders
Veamos,conmsdetalle,unficheroderutas.Lasreglassevanmatcheando enordendedefinicin,porloqueel ordens queimporta.
#Symfony/src/curso/e5Bundle/Resources/config/routing.yml cursoe5Bundle_blog: pattern:/blog/{slug} defaults:{_controller:cursoe5Bundle:Default:index }
pattern:DefineunpatrnquesecotejacontralaURLdelRequest.Enestecaso,elpatrndefinidoparacursoe5Bundle_bloghar matchconrutasdeltipo /blog/*Porejemplo,sielRequest sehacea/blog/post1elvalordelavariableslug tomar elvalor post1(yestavariableestar disponibleenelController). _controller:IndicaaSymfony qu controladordeberaejecutarcuandolaURLsolicitadaseajustealpatrndefinido.Sellamaal controladorporsunombrelgico(sereferenciaaunaclaseymtodoespecficos).
//cursoe5Bundle:Default:index >src/curso/e5Bundle/Controller/DefaultController.php (mtodoindexAction) class DefaultController{ public function indexAction($slug){ //disponibleelvalordelavariable(aka placeholder)slug. ... return new Response(...); } }
46
ROUTING:ejercicioR0
Generarlaconfiguracinnecesariaparaque:
URL ejercicioR0/Miguel/Martin/18 muestre TellamasMiguelMartin ytienes18aos
47
ROUTING:ejercicioR0
48
ROUTING:placeholders defaults
DeseamosquelaaplicacinrespondaavariasURLs (losplaceholders debentenerunvalorpor defecto).Procedemosameterlosenlalneadefaults yasignarlesunvalorpordefecto. NtesequesecreanvariasrutasposiblesconUNsolopattern.
49
ROUTING:ejercicioR1
GenerarlaconfiguracinnecesariaparaquelassiguientesURLs secomportencomoseenuncia
URL URL URL URL ejercicioR1/Miguel/Martin/18 ejercicioR1/Miguel/Martin ejercicioR1/Miguel ejercicioR1/ muestre muestre muestre muestre TellamasMiguelMartin ytienes18aos TellamasMiguelMartin ytienesXaos TellamasMiguelAPELLIDOytienesXaos TellamasNOMBREAPELLIDOytienesXaos
Conunanicaaccin definidaenelcontrolador
50
ROUTING:Requirements sobreplaceholders
Sinuestrocontroladornocontrola()queelparmetro$edadseanumrico,podemosencontrarsituaciones problemticas.Pero,paraqu pasaralcontroladoralgoquenonosinteresa?Mejordetectarlo antes Symfony nospermiteasegurarnos,desdeelficheroderutas,dequeelpatrnsolodebehacermatch siformadopor unoomsdgitos(\d+). Losrequirements sonexpresionesregulares.Setrata,portanto,deunmecanismo bastantepotente: idioma:en|fr|es|de
#routing.yml cursoe5Bundle_ejercicioR1: pattern:/ejercicioR1/{nombre}/{apellido}/{edad} defaults:{_controller:cursoe5Bundle:Default:ejercicioC1,nombre:MINOMBRE,apellido:MIAPELLIDO,edad:X} requirements: edad:\d+
Qu sucedesiesperamosquelarutaseainvocadaporPOST,conciertainformacin,y nosllegaporGET?
51
ROUTING:Requirements demtodo
Sinuestrocontroladornocontrola()cmoseharealizadoelRequest,podemosencontrar situacionesproblemticas.Pero,paraqu pasaralcontroladoralgoquenonosinteresa?Mejor detectarloantes Symfony nospermiteasegurarnos,desdeelficheroderutas,dequeelpatrnsolodebehacermatch si elRequest seharealizadoporelmtodo deseado(GET,HEAD,POST,PUT,DELETE). Sinoseindicamtodoalguno,larutadefinidahar matchparacualquiermtodo!
#routing.yml cursoe5Bundle_ejercicioR1: pattern:/ejercicioR1/{nombre}/{apellido}/{edad} defaults:{_controller:cursoe5Bundle:Default:ejercicioC1,nombre:MINOMBRE,apellido:MIAPELLIDO,edad:X} requirements: edad:\d+ _method:GET
52
ROUTING:Rutasavanzadasyparmetros
especiales
article_show: pattern:/articles/{culture}/{year}/{title}.{_format} defaults:{_controller:AcmeDemoBundle:Article:show,_format:html } requirements: culture:en|fr _format:html|rss year:\d+
Qu quiere decir esto? Esta ruta solo se matchear si: * la url empieza por /articles y contiene al menos los parmetros culture,year y title * El parmetro culture tiene valor igual a en o fr * El parmetro year est formado por ms de un dgito * El parmetro _format es opcional. De existir, su valor debe ser html o rss. Su valor (si se omite) es html
Vemos, adems, algo nuevo y muy til: el parmetro _format Podemos usar este valor para indicar el ContentType de la respuesta y devolver distintos resultados (por ejemplo, renderizar con distintos templates) para distintos valores del parmetro _format. En este sentido, por ejemplo, una peticin con _format=json se traducira en una respuesta de tipo Content Type application/json 53
ROUTING:parmetrosespeciales
Parmetros especiales (empiezan por _): _method: _controller: _format: _locale: _scheme: Determina el mtodo de la peticin (POST, GET,) Determina qu controlador se ejecutar cuando la ruta haga match Utilizado para determinar el formato de la peticin Utilizado para determinar los valores de locale de la sesin Si se settea con https fuerza a que se use siempre HTTPS (al generar las rutas tambin se generarn con https, y si hacemos un request http, automticamente se redireccionar a https)
Como el valor de locale se almacena en la sesin del usuario, puede resultar tentador utilizar la misma URL para mostrar un mismo recurso en distintos idiomas, en funcin del valor almacenado en el locale del cliente. Por ejemplo, www.misitio.com/contact podra mostrar el contenido el ingls para un usuario y francs en otro. Esto supone una violacin de principio bsico de la Web: un mismo recurso debe devolver el mismo recurso con independencia del usuario. Adems, qu idioma debera ser indexado por los crawlers de buscadores? Es ms conveniente incluir el valor de locale en la URL. Al utilizar el parmetro especial _locale, cuando el usuario visite la URI /de/contact, el valor de locale ser automticamente setteado a de en la sesin del usuario ($session>setLocale('fr');) A partir de ese momento se puede usar el valor de locale para crear rutas a otras pginas traducidas en la
aplicacin.
ROUTING:ejercicioR2
(0) Borrar la cach del navegador, informacin de sesiones, etc (firefox: SHIFT+CTRL+SUPR). Al visitar /ejercicioR2 se debe mostrar el valor de sesin locale actual Alvisitar/en/ejercicioR2 sedebemostrarelvalordelasesinlocale yCAMBIARelmismoa en Alvisitar/de/ejercicioR2 sedebemostrarelvalordelasesinlocale yCAMBIARelmismoa de Alvisitar/fr/ejercicioR2 sedebemostrarelvalordelasesinlocale yCAMBIARelmismoa fr Alvisitar/es/ejercicioR2 sedebemostrarelvalordelasesinlocale yCAMBIARelmismoa es
(PISTA:RECUERDAlasSESIONES(explicadasenelcaptuloControlador)y PISTA2:$this>getRequest()>getSession()>getLocale())
Msavanzado:intentamostrarlabanderaasociadaallocale actual
Lectura interesante:
http://damnhandy.com/2007/11/19/urivsurlwhatsthedifference/
56
ROUTING:visualizacin&debugging
Comando para mostrar informacin sobre una ruta (nombreruta) o todas, en ausencia de parmetro
57
ROUTING:generacindeURLs (desdeelcontrolador)
En realidad, el sistema de rutas es un mapeado bidireccional: - De URL a controlador+parmetros [match] //enelcontrolador... $params =$router>match('/blog/myblogpost'); //array('slug'=>'myblogpost','_controller'=>'AcmeBlogBundle:Blog:show')
De controlador+parmetros a URL [generate] RUTA RELATIVA (por defecto!) //enelcontrolador...(class XXXextends Controller) $uri =$this>get('router')>generate('blog_show',array('slug'=>'myblogpost')); ///blog/myblogpost
Tambin funciona desde Javascript utilizando FOSJsRoutingBundle: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle var url = Routing.generate('blog_show', { "slug": 'my-blog-post});
RUTA ABSOLUTA (tercer parmetro con valor = true) //enelcontrolador...(class XXXextends Controller) $uri =$this>get('router')>generate('blog_show',array('slug'=>'myblogpost'),true); //http://www.misitio.com/blog/myblogpost //elHOSTconelquegeneraURLs absolutasesaqulalquesehareferenciadoenelRequest (lo tomade//laserver information dePHP) //Parascriptsdesdelneadecomandoshayquesettearlo explcitamente: //$request>headers>set(HOST,www.misitio.com);
58
{_controller:cursoe5Bundle:Default:hello,nombre:SINNOMBRE}
Si generamos una ruta hacia ese sitio con valor en el parmetro distinto al valor por defecto $router>generate(cursoe5Bundle_helloworld,array(nombre =>Miguel)); //produce:/curso/hello/Miguel Sin embargo, si generamos con los valores por defecto, tendremos $router>generate(cursoe5Bundle_helloworld,array(nombre =>SINNOMBRE)); //produce:/curso/hello
59
ROUTING:ejercicioR3
CLASE ROUTER: http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html CLASE ROUTE: http://api.symfony.com/2.0/Symfony/Component/Routing/Route.html CLASE ROUTE COLLECTION: http://api.symfony.com/2.0/Symfony/Component/Routing/RouteCollection.html
En /ejercicioR3 generaremos links a algunas de las pginas creadas. En lugar de hacerlo como en el caso anterior (generbamos el link completo en el controlador) nos limitaremos a crear un array con valores de links del tipo: $links=array(/enlace0,/enlace1); Utiliza la plantilla siguiente (que debes guardar como Resources/views/Default/ejercicioR3.html.twig)
<!DOCTYPEhtml PUBLIC"//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"dir="ltr"lang="enUS"xmlns:fb="http://www.facebook.com/2008/fbml" xmlns:addthis="http://www.addthis.com/help/apispec"> <head> <title>CursoSymfony</title> </head> <body> {%if links%} {%for linkinlinks%} <li><ahref="{{link|e }}">{{link|e }}</a></li> {%endfor %} {%else %} Nolinks,noparty! {%endif %} </body> </html>
Utiliza esta lnea para devolver Response renderizando con plantilla return $this>render('cursoe5Bundle:Default:ejercicioR3.html.twig',array('links'=>$rutas));
60
ROUTING:ejercicioR4 (desdeelcontrolador)
(2) Mejorar el cdigo para que se muestren los nombres de los links en lugar de los valores a los que apuntan Renderizar con la siguiente plantilla ejercicioR3.html.twig: <!DOCTYPEhtml PUBLIC"//W3C//DTDXHTML1.0Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"dir="ltr"lang="enUS" xmlns:fb="http://www.facebook.com/2008/fbml"xmlns:addthis="http://www.addthis.com/help/api spec"> <head> <title>CursoSymfony</title> </head> <body> {%if links%} {%for key,value inlinks%} <li><ahref="{{value|e }}">{{key|e }}</a></li> {%endfor %} {%else %} Nolinks,noparty! {%endif %} </body> </html>
61
ROUTING:ejercicioR5
CLASEROUTER:http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html CLASEROUTE:http://api.symfony.com/2.0/Symfony/Component/Routing/Route.html CLASEROUTECOLLECTION:http://api.symfony.com/2.0/Symfony/Component/Routing/RouteCollection.html
(3) Mejorar el cdigo para que se generen los nombres, urls y valores por defecto de forma automtica a partir del catlogo de rutas definidas en nuestro proyecto. nicamente tener en cuenta aqullas rutas que comiencen con /curso. Renderizar con la siguiente plantilla ejercicioR5.html.twig: <!DOCTYPEhtml PUBLIC"//W3C//DTDXHTML1.0Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"dir="ltr"lang="enUS" xmlns:fb="http://www.facebook.com/2008/fbml"xmlns:addthis="http://www.addthis.com/help/api spec"> <head> <title>CursoSymfony</title> </head> <body> {%if links%} {%for key,value inlinks%} <li><ahref="{{value|e }}">{{key|e }}</a></li> {%endfor %} {%else %} Nolinks,noparty! {%endif %} {%if outputis defined %} <pre>{{output|raw }}</pre> {%endif %} </body> </html>
62
63
CONTROLLER (CONTROLADOR)
TEORA:
Definicinymecanismo Controladorbsico Parmetros BaseController yejemplos Renderizadodeplantillas(invocacinavista) Otrosservicios Mensajesflash Manejodeerrores Manejodesesiones ObjetoResponse
64
CONTROLADOR
Controlador =funcinquetomainformacindelapeticinHTTPyconstruyeuna respuesta(XML,HTML,imagen,error404,etc).Contienelalgicaquelaaplicacin necesitapararenderizar elcontenidodeunapgina:lecturadelapeticin,cargar informacindelaBD,enviarunemailogestionarsesionesdeusuario.
use Symfony\Component\HttpFoundation\Response; public function helloAction() { return new Response('Helloworld!'); }
Ejemplos: ControladorA:preparaResponse conelcontenidodelahomepage. ControladorB:leeunparmetro(id)delRequest,consultaalaBDelusername asociadoaesaIDygenerala Response.Sinoexisteusername asociadoaeseid,produceunaRespuestacon404(noencontrado). ControladorC:leelainformacindeloscamposdelformulariodelRequest, almacenalosresultadosenlaBD, mandaunemailalwebmasteryproduceunaResponse mostrndolealclienteelmensajeMuchasgraciaspor rellenarelformulario.
65
CONTROLADOR:Mecanismo
REQUEST
FRONTCONTROLLER
(app.php oapp_dev.php)
Ejecucindel
CONTROLADOR
asociadoaesaruta
MODELO
TEMPLATING
creacinde Respuesta. 66
Normalmente,uncontroladoresunmtodosimpledentrodeunobjetocontrolador.Tambinselesconoceporacciones. //src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController { Secomienzadeclarandoelnamespace (PHP5.3)eimportandolaclase Response. Elnombredelaclasesedefinecomoelnombredelaclasedelcontrolador seguidodelapalabraController.Deestaformasepermitequesean referenciadosporlaprimeraparte(Hello)delnombreenlaconfiguracin derutas. CadaaccindelaclasedelcontroladorsenombraconelsufijoAction yse referenciaenelficherodeconfiguracinderutasporelnombredela accin(index). Elparmetro$name seconocecomoplaceholder (msdetallesencaptulo derouting). return new Response('<html><body>Hello '.$name.'! </body></html>'); } } Porltimo,segeneralarespuesta.
CONTROLADORBsico
67
CONTROLADOR:Parmetros
<?php //src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function indexAction($first_name,$last_name,$color) { //... } }
Elordendelosargumentosen elcontroladorNOIMPORTA. Loques importaesquetodos losparmetrosdefinidosenel controladorestnpresentesen elficheroderutas* Notodoslosargumentosdela rutadebenestarenel controlador**
68
CONTROLADOR:ObjetoResponse
Lo nico obligatorio para un controlador es devolver un objeto Response.
http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html //createasimpleResponsewitha200statuscode(the default) $response =new Response('Hello'.$name,200); //createaJSONresponsewitha200statuscode $response =new Response(json_encode(array('name'=>$name))); $response>headers>set('ContentType','application/json');
Existe el objeto HeaderBag con varios mtodos que permiten leer y modificar las cabeceras del Response.
http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/HeaderBag.html
69
CONTROLADOR:Parmetros
Se puede disponer del objeto Request completo. Especialmente til para formularios. Existen multitud de mtodos definidos para el objeto Request:
http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html
70
CONTROLADOR:Class BaseController
Symfony viene con una clase llamada Controller que incluye facilidades para desarrollar las tareas ms comunes de los controladores y facilitar el acceso a los recursos necesarios: http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html Haciendo un extend de la clase Controller, podemos utilizar diversos mtodos.
//src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } }
71
CONTROLADOR:Class BaseController
Veamos algunas de las tareas habituales de un controlador, apoyadas por funciones de la clase Controller.
//src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller { public function indexAction() { //REDIRECTING: //generateUrl crealaURLparaunarutadefinida. //elmtodoredirect realizaunaredireccin302(temporal). //parared.301,indicarloenelsegundoparmetro:$this>redirect($this>generateUrl('homepage'),301); //LarutaindicadaalafuncingenerateUrl DEBECOINCIDIRINTEGRAMENTECONLODECLARADOENFICHDERUTAS! return $this>redirect($this>generateUrl('cursoe5Bundle_homepage '));
cursoe5Bundle_homepage: pattern:/ defaults:{_controller:cursoe5Bundle:Default:index }
//FORWARDING: //Tambinsepuededelegarelcontrolenotrocontrolador,enlugarderedireccionarlaURLdelcliente. //Elmtodoforward devuelveunobjetoResponse,resultadodeejecutarelcontroladorespecfo. $response =$this>forward('AcmeHelloBundle:Hello:fancy',array( 'name'=>$name, //AcmeHelloBundle/Controller/HelloController 'color'=>'green' )); public function fancyAction($name,$color){ //modificar$responsealgustoodevolverlodirectamente... return $response; } }
//...create and return aResponseobject }
72
CONTROLADOR:Class BaseController
73
CONTROLADOR:Renderizadode plantillas(generarVISTA)
Otra de las tareas comunes es generar la salida de nuestra aplicacin (normalmente, formato HTML). El mtodo renderView() devuelve el contenido de haber renderizado una plantilla (template). Con este contenido, se puede devolver la respuesta.
$content =$this>renderView('AcmeHelloBundle:Hello:index.html.twig',array('name'=>$name)); //llamadaarenderizadode:Resources/views/Hello/index.html.twig return new Response($content);
74
Ejercicios
En/inforequest mostrarlainformacindel Request (Consejo:usaprint_r($VAR,1) y<pre>)
CONTROLADOR:Otrosservicios
En ocasiones es interesante acceder a servicios de otras clases utilizando el mtodo get. Para listar todas los existentes, ejecutar: php app/console container:debug
Algunosdelosquesuelenutilizarsemsamenudo:
76
CONTROLADOR:Manejodeerroresy pginas404
Cuando el recurso solicitado no se encuentra disponible, se debe generar la respuesta HTTP404. Al hacer extends de la clase Controller, procedemos asi:
public function indexAction() { $product =//retrievethe objectfromdatabase if (!$product){ throw $this>createNotFoundException('Theproductdoesnotexist'); } return $this>render(...); }
77
Ejercicio:generarException
En/division obtenerdosnmerosaleatorios(numeradorentre0y10, denominadorentre0y2) Realizarladivisinentreellos Contemplarlaposibilidaddequeeldenominadorseaceroygenerar excepcinentalcaso. AYUDA:http://php.net/manual/es/function.rand.php
CONTROLADOR:Manejodesesiones
Symfony proporciona un objeto de sesiones para almacenar informacin del usuario entre peticiones. $session =$this>getRequest()>getSession(); //storeanattributeforreuseduringalateruserrequest $session>set('foo','bar'); //inanothercontrollerforanotherrequest $foo =$session>get('foo'); //setthe userlocale $session>setLocale('fr');
79
CONTROLADOR:EJERCICIO Manejodesesiones
En/cuantasveces Deberesidiruncontadorquenosindiquecuntasvecesnoshemosconectadoadicha pgina.
80
CONTROLADOR:Manejodecookies
//paralageneracindeCookies... useSymfony\Component\HttpFoundation\Cookie; useSymfony\Component\BrowserKit\Request;
/*Generaunacookiellamada'COOKIE_MIGUEL'*/ public function generaCookieAction(){ $mi_cuki =new Cookie('COOKIE_MIGUEL',$value ="valor",$expire=0,$path ='/',$domain =null,$secure =false,$httpOnly =true); //opc1:renderizarsintemplates $response=new Response('<html><head><title>GeneroCookie</title></head><body>Enmi respuestavalacookie...</body></html>'); $response>headers>setCookie($mi_cuki); return $response;
81
CONTROLADOR:Manejodecookies II
/*Leelacookiellamada'COOKIE_MIGUEL'siexiste,omuestratodaslascookies public function leeCookieAction(){ $request =$this>get('request'); $cookies =$request>cookies; if ($cookies>has('COOKIE_MIGUEL')){ return new Response("Encontrada'COOKIE_MIGUEL':<pre>".print_r($cookies >get('COOKIE_MIGUEL'),1)."</pre>"); } return new Response("<pre>".print_r($cookies,1)."</pre>"); } */
82
CONTROLADOR:Mensajesflash
Tambin puedes almacenar pequeos mensajes en la sesin del usuario. Estos mensajes estarn disponibles nicamente en la siguiente peticin. Esto es til al procesar formularios. En este ejemplo se procesa el formulario, se muestra este mensaje flash y se realiza la redireccin. Para que este mensaje aparezca en la pantalla del usuario, deber estar contemplado en la plantilla.
{%if app.session.hasFlash('notice')%} public function updateAction() { $form =$this>createForm(...); $form>bindRequest($this>getRequest()); if ($form>isValid()){ //dosomesortofprocessing <div class="flashnotice"> {{ app.session.flash('notice')}} </div> {%endif %}
83