templating (3/3) | train to symfony
Post on 05-Dec-2014
852 Views
Preview:
DESCRIPTION
TRANSCRIPT
http://traintosymfony.com1 Emanuele Gaspari
TRAINTO SYMFONY
Verona, 13•14 Aprile 2013
the frameworkshop
http://traintosymfony.com@TrainToSymfony
Media partner:
Templating (3/3)
http://traintosymfony.com2 Emanuele Gaspariabout me
creatore di SymfonyBricks
Emanuele Gaspari
ultimo sito pubblicato (copiaincolla): www.ilovesanmartino.it4 bundles21 controllers6 services9 entities5 repositories custom4 estensioni twig14 macros10 bundles aggiuntivi(FOSUserBundle, FOSJsRoutingBundle, KnpPaginatorBundle, CopiaincollaMetaTagsBundle, etc..)
co-sviluppatore di CopiaincollaMetaTagsBundle https://github.com/copiaincolla/MetaTagsBundle
https://symfonybricks.com
inmarelibero@gmail.com @inmarelibero
http://traintosymfony.com3 Emanuele Gaspari
Symfony: teoria e codice
Routing Templating
frameworkshop: il programma
overview
obiettivo della terza parte:
i Templates in Symfony
http://traintosymfony.com4 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com5 Emanuele Gaspariil template engine TWIG
Un template engine (web) è un software progettato per processare un template e dei contenuti, per produrre in output un documento web
Symfony integra il template engine TWIG per il renderingdelle pagine html (e non solo)
http://en.wikipedia.org/wiki/Template_engine_(web)
Train toSymfony6
...posso usare ancora php, come prima?
http://traintosymfony.com7 Emanuele Gaspari
sì
http://traintosymfony.com8 Emanuele Gaspari
se vuoi ancora scrivere codice di questo tipo:
http://traintosymfony.com9 Emanuele Gaspario_O
<html> <head> <title>[...]</title> [...] </head>
<body> <h3>I miei gruppi preferiti</h3> <ul> <?php $info = new BandsManager(true, 12); foreach($bands as $band_name) { $band_info = $info->findInfos($band); echo “<li>”.$band_name; if(!is_null($band_info)) { echo ‘(<a href=”‘.$band_info->site.’”>’.$band_info->site.’</a>)’; } echo “</li>”; } ?> </ul> </body>
</html>
http://traintosymfony.com10 Emanuele Gaspari
http://traintosymfony.com11 Emanuele Gaspariperché un template engine
● separazione tra contenuto (template) e logica (backend, php)● organizzazione del codice secondo il paradigma MVC
perché adottare un template engine, invece che PHP semplice
http://traintosymfony.com12 Emanuele Gasparimmh, non è sufficiente
non sono ancora convinto
http://traintosymfony.com13 Emanuele Gaspari
● flessibile● veloce● sicuro
● blocchi● ereditarietà
● sviluppato da SensioLabs● è abilitato di default in Symfony
perché twig
http://traintosymfony.com14 Emanuele Gasparimmh, non è sufficiente (ancora)
interessante, ma ancora non basta
http://traintosymfony.com15 Emanuele Gaspari
sono due le caratteristiche che fanno preferire TWIG
Train toSymfony16
*non a scapito della potenza
1) semplicità, eleganza
http://traintosymfony.com17 Emanuele Gasparisemplicità, eleganza
<h1>I miei gruppi preferiti</h1>
<ul> {% for band in bands %} <li> {{ band.name }}
{% if band.website %} (<a href=”{{ band.website }}”>official site</a>) {% endif %} </li> {% endfor %}</ul>
Train toSymfony18
*e sono tutti contenti
2) possono metterci le mani sia web designer che sviluppatori
http://traintosymfony.com19 Emanuele Gaspari
http://traintosymfony.com20 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com21 Emanuele GaspariTemplate Naming Pattern
per riferirmi ad un template uso il nome logico, che ha la sintassi:
# src/Tts/DemoBundle/Resources/view/Prodotto/show.html.twig# src/Tts/DemoBundle/Resources/view/Prodotto/show.html.twig
bundle:controller:template
TtsDemoBundle:Prodotto:show.html.twig
Bundle Controller TemplateTtsDemoBundleTtsDemoBundle ProdottoControllerProdottoController show.html.twigshow.html.twig
http://traintosymfony.com22 Emanuele Gaspari
i templates si trovano di default in Resources/views
all'interno le cartelleResources/views/[nome controller]rispecchiano i Controller di un bundle
SymfonyBricksSiteBundle::layout.html.twigSymfonyBricksSiteBundle::layout.html.twig
http://traintosymfony.com23 Emanuele Gasparitip
anche TWIG supporta i namespace(Symfony2)
TtsDemoBundle:Prodotto:show.html.twig
http://symfony.com/doc/current/cookbook/templating/namespaced_paths.html
@TtsDemoBundle/Prodotto/show.html.twig
http://traintosymfony.com24 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com25 Emanuele Gaspari
come Symfony sceglie quale template renderizzare per una certa azione
dalla action al template corretto
http://traintosymfony.com26 Emanuele Gaspari
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
class ProdottoController extends Controller{
public function showAction($codice) { $entity = $this->getRepository('TtsDemoBundle:Prodotto')->findOneBy( array(“codice” => $codice) );
return $this->render( 'TtsDemoBundle:Prodotto:show.html.twig', array('entity' => $entity) ); }}
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
class ProdottoController extends Controller{
public function showAction($codice) { $entity = $this->getRepository('TtsDemoBundle:Prodotto')->findOneBy( array(“codice” => $codice) );
return $this->render( 'TtsDemoBundle:Prodotto:show.html.twig', array('entity' => $entity) ); }}
# src/Tts/DemoBundle/Resources/view/Prodotto/show.html.twig
$this->render()
la action restituisce un template twig compilato,specificato nella action
http://traintosymfony.com27 Emanuele Gaspari
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/** * @Template() */public function showAction($codice){ $entity = $this->getRepository('TtsDemoBundle:Prodotto')->findOneBy( array(“codice” => $codice) ); return array(“entity” => $entity);}
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/** * @Template() */public function showAction($codice){ $entity = $this->getRepository('TtsDemoBundle:Prodotto')->findOneBy( array(“codice” => $codice) ); return array(“entity” => $entity);}
# src/Tts/DemoBundle/Resources/view/Prodotto/show.html.twig
sfrutto l'annotazione @Template()
@Template()
http://traintosymfony.com28 Emanuele Gaspari
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/** * @Template(“TtsDemoBundle:Prodotto:show_offerta.html.twig”) */public function showAction($codice){
[…] return array(“entity” => $entity);}
# src/Tts/DemoBundle/Controller/ProdottoController.php
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/** * @Template(“TtsDemoBundle:Prodotto:show_offerta.html.twig”) */public function showAction($codice){
[…] return array(“entity” => $entity);}
# src/Tts/DemoBundle/Resources/view/Prodotto/show_offerta.html.twig
utilizzo l'annotazione @Template() specificando un parametro
@Template(...)
http://traintosymfony.com29 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com30 Emanuele Gasparisintassi
sintassi speciale di TWIG
{{ ... }}“dire” qualcosa
{% ... %}“fare” qualcosa
http://traintosymfony.com31 Emanuele Gasparivariabili
{{ prodotto['codice'] }}{{ prodotto['codice'] }}
{{ nomeProdotto }}{{ nomeProdotto }}
{{ ... }} stampa il contenuto di una variabile
nomeProdotto è una stringa
prodotto è un array associativo
{{ prodotto.codice }}{{ prodotto.codice }}
prodotto potrebbe essere un array o un oggetto
http://traintosymfony.com32 Emanuele Gasparioperatore .
l'operatore . si comporta diversamente in base alla variabile
● prodotto è un array e codice una chiave● prodotto è un oggetto e codice una proprietà accessibile● prodotto è un oggetto e codice() un metodo accessibile● prodotto è un oggetto e getCodice() un metodo accessibile● prodotto è un oggetto e isCodice() un metodo accessibile
{{ prodotto.codice }}{{ prodotto.codice }}
verifica se:
http://traintosymfony.com33 Emanuele Gasparidichiarare una variabile
{% set nomeProdotto = 'Chiave inglese' %}{% set nomeProdotto = 'Chiave inglese' %}
{% set ... %} permette di inizializzare variabili all'interno del template TWIG
{% set nomeProdottoEsteso = nomeProdotto ~ “ (Attrezzi da lavoro)” %}{% set nomeProdottoEsteso = nomeProdotto ~ “ (Attrezzi da lavoro)” %}
concatenazione di stringhe
{% set misureDisponibili = [12, 24, 36] %}{% set misureDisponibili = [12, 24, 36] %}
{% set misureDisponibiliQuantita = {'12': 128, '24': 2, '36': 74} %}{% set misureDisponibiliQuantita = {'12': 128, '24': 2, '36': 74} %}
http://traintosymfony.com34 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com35 Emanuele Gasparifiltri
{{ nomeProdotto | upper }}{{ nomeProdotto | upper }}
le variabili posso essere modificate dai filtri
i filtri si applicano alla variabile separandoli con |,e possono accettare dei parametri
{{ misureDisponibili | join(“, ”) }}{{ misureDisponibili | join(“, ”) }}
http://twig.sensiolabs.org/doc/filters/index.html
http://traintosymfony.com36 Emanuele Gasparifiltri più comuni
{{ catalogo.ultimoAggiornamento | date(“m/d/Y”) }}{{ catalogo.ultimoAggiornamento | date(“m/d/Y”) }}
{{ misureDisponibili | join(“, ”) }}{{ misureDisponibili | join(“, ”) }}
{% if misureDisponibili | length > 0 %}...
{% endif %}
{% if misureDisponibili | length > 0 %}...
{% endif %}
{{ prodotto.descrizioneEstesa | nl2br }}{{ prodotto.descrizioneEstesa | nl2br }} {{ prodotto.descrizioneEstesa | raw }}{{ prodotto.descrizioneEstesa | raw }}
{% for i in misureDisponibili | slice(1, 5) %}...
{% endfor %}
{% for i in misureDisponibili | slice(1, 5) %}...
{% endfor %}
http://traintosymfony.com37 Emanuele Gaspariconcatenazione
{{ misureDisponibili | slice(1, 5) | join(separator) }}{{ misureDisponibili | slice(1, 5) | join(separator) }}
{{ prodotto.descrizioneEstesa | nl2br | raw }}{{ prodotto.descrizioneEstesa | nl2br | raw }}
i filtri possono essere concatenati
http://traintosymfony.com38 Emanuele Gasparifunzioni
le funzioni generano contenuto
si possono utilizzare sia all'interno di {{ ... }} che di {% ... %}
{% for i in range(1, 100) %}{% for i in range(1, 100) %}
{{ random(100) }}{{ random(100) }}
http://twig.sensiolabs.org/doc/functions/index.html
http://traintosymfony.com39 Emanuele Gasparifunzioni comuni
{{ dump(misureDisponibili) }}{{ dump(misureDisponibili) }}
{{ include(template_from_string("Codice prodotto: {{ prodotto.codice }}") }}{{ include(template_from_string("Codice prodotto: {{ prodotto.codice }}") }}
{{ random(['red', 'blue', 'yellow']) }}{{ random(['red', 'blue', 'yellow']) }}
{% for i in range(0, 3) %}...
{% endfor %}
{% for i in range(0, 3) %}...
{% endfor %}
http://traintosymfony.com40 Emanuele Gasparicombinazione di funzioni
{% for i in range( random(100) ) %}{% for i in range( random(100) ) %}
le funzioni possono essere utilizzate insieme
http://traintosymfony.com41 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com42 Emanuele Gasparistrutture di controllo
gli operatori per le strutture di controllo in TWIG sono detti tag
{% if misureDisponibili | length > 0 %}<ul>
{% for misura, quantita in misureDisponibiliQuantita %} <li>
{{ quantita }} pezzi sono disponibiliper la misura “{{ misura }}”
</li> {% endfor %} </ul>{% else %}
Spiacenti, questo articolo non è disponibile in alcuna misura{% endif %}
{% if misureDisponibili | length > 0 %}<ul>
{% for misura, quantita in misureDisponibiliQuantita %} <li>
{{ quantita }} pezzi sono disponibiliper la misura “{{ misura }}”
</li> {% endfor %} </ul>{% else %}
Spiacenti, questo articolo non è disponibile in alcuna misura{% endif %}
http://twig.sensiolabs.org/doc/tags/index.html
http://traintosymfony.com43 Emanuele Gasparistrutture di controllo
i tag possono anche effettuare delle operazioni sul loro contenuto
{% filter lower | capitalize %} {{ prodotto.descrizione }}{% endfilter %}
{% filter lower | capitalize %} {{ prodotto.descrizione }}{% endfilter %}
{% macro printDescrizioneProdotto(prodotto) %} Descrizione prodotto: {{ prodotto.codice }} - {{ prodotto.descrizione | raw }}{% endmacro %}
{{ _self.printDescrizioneProdotto(entity) }}
{% macro printDescrizioneProdotto(prodotto) %} Descrizione prodotto: {{ prodotto.codice }} - {{ prodotto.descrizione | raw }}{% endmacro %}
{{ _self.printDescrizioneProdotto(entity) }}
{% spaceless %} <div> <strong>foo</strong> </div>{% endspaceless %}
{% spaceless %} <div> <strong>foo</strong> </div>{% endspaceless %}
http://traintosymfony.com44 Emanuele Gasparicombinazione di tags
i tags possono essere utilizzati insieme
{% macro printDescrizioneProdotto(prodotto) %} Descrizione prodotto: {{ prodotto.codice }} -
{% filter lower | capitalize %}{{ prodotto.descrizione | raw }}
{% endfilter %}{% endmacro %}
{% spaceless %}{{ _self.printDescrizioneProdotto(entity) }}
{% endspaceless %}
{% macro printDescrizioneProdotto(prodotto) %} Descrizione prodotto: {{ prodotto.codice }} -
{% filter lower | capitalize %}{{ prodotto.descrizione | raw }}
{% endfilter %}{% endmacro %}
{% spaceless %}{{ _self.printDescrizioneProdotto(entity) }}
{% endspaceless %}
http://traintosymfony.com45 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com46 Emanuele Gaspariconfronti e test
TWIG offre una sintassi semplice per effettuare confronti e test
http://traintosymfony.com47 Emanuele Gasparioperatori di confronto
operatori di confronto
{% if prodotto.codice == '127' %}{% if prodotto.codice == '127' %}
== != < > >= <=== != < > >= <=
{% if misureDisponibili | length > 0 %}{% if misureDisponibili | length > 0 %}
http://traintosymfony.com48 Emanuele Gasparioperatore test
l'operatore is effettua dei test su una variabile
{% if loop.index is divisibleby(3) %}{% if loop.index is divisibleby(3) %}
{% if loop.index is even %}{% if loop.index is even %}
http://twig.sensiolabs.org/doc/tests/index.html
{% if loop.index is not divisibleby(3) %}{% if loop.index is not divisibleby(3) %}
http://traintosymfony.com49 Emanuele Gasparioperatore ternario
come in PHP, anche in TWIG esiste un operatore ternario
{% for misura in misureDisponibili %}<tr class=”{{ ( loop.index is even ) ? “even” : “odd” }}”>[...]
{% endfor %}
{% for misura in misureDisponibili %}<tr class=”{{ ( loop.index is even ) ? “even” : “odd” }}”>[...]
{% endfor %}
http://twig.sensiolabs.org/doc/tests/index.html
http://traintosymfony.com50 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com51 Emanuele Gaspariereditarietà di templates
la killer feature di TWIG è il meccanismo di ereditarietà di templates
in ogni template è possibile definire dei blocchiil cui contenuto può essere sovrascritto
un template può estendere un altro templateper sfruttare le parti comuni e
sovrascriverne altre
http://traintosymfony.com52 Emanuele Gaspariereditarietà di templates
# src/Tts/DemoBundle/Resources/views/layout.html.twig
<!DOCTYPE html><html>
<head><title>{% block title %}workshop{% endblock %} | Train to Symfony</title>
</head> <body>
<div id="main">{% block main %}{% endblock %}
</div> <div id="footer"> {% block footer %}
Train to Symfony - Verona, 13•14 Aprile 2013 {% endblock %} </div> </body></html>
# src/Tts/DemoBundle/Resources/views/layout.html.twig
<!DOCTYPE html><html>
<head><title>{% block title %}workshop{% endblock %} | Train to Symfony</title>
</head> <body>
<div id="main">{% block main %}{% endblock %}
</div> <div id="footer"> {% block footer %}
Train to Symfony - Verona, 13•14 Aprile 2013 {% endblock %} </div> </body></html>
definisco un template di base (es. layout), che imposta la struttura generale
http://traintosymfony.com53 Emanuele Gaspariereditarietà di templates
# src/Tts/DemoBundle/Resources/views/Default/about.html.twig
{% extends "TtsDemoBundle::layout.html.twig" %}
{% block title %}about{% endblock %}
{% block main %} <h1>Train to Symfony</h1> <p>
Train to Symfony è un puntualissimo convoglio che ti offre un corsofortemente pratico su questo sempre più utilizzato framework.
</p>{% endblock %}
# src/Tts/DemoBundle/Resources/views/Default/about.html.twig
{% extends "TtsDemoBundle::layout.html.twig" %}
{% block title %}about{% endblock %}
{% block main %} <h1>Train to Symfony</h1> <p>
Train to Symfony è un puntualissimo convoglio che ti offre un corsofortemente pratico su questo sempre più utilizzato framework.
</p>{% endblock %}
creo un template che lo estende, e sovrascrivo solo i blocchi che mi servono
http://traintosymfony.com54 Emanuele Gaspariereditarietà di templates
<!DOCTYPE html><html>
<head><title>about | Train to Symfony</title>
</head> <body>
<div id="main"> <h1>Train to Symfony</h1>
<p>Train to Symfony è un puntualissimo convoglio che ti offre un corsofortemente pratico su questo sempre più utilizzato framework.
</p></div>
<div id="footer">Train to Symfony - Verona, 13•14 Aprile 2013
</div> </body></html>
<!DOCTYPE html><html>
<head><title>about | Train to Symfony</title>
</head> <body>
<div id="main"> <h1>Train to Symfony</h1>
<p>Train to Symfony è un puntualissimo convoglio che ti offre un corsofortemente pratico su questo sempre più utilizzato framework.
</p></div>
<div id="footer">Train to Symfony - Verona, 13•14 Aprile 2013
</div> </body></html>
il risultato è la combinazione tra layout.html.twige i blocchi che sono stati sovrascritti
http://traintosymfony.com55 Emanuele Gaspariregole
ogni template può estendere al massimo un template
ogni template può essere esteso da un altro template
l'ereditarietà si concatena
http://traintosymfony.com56 Emanuele Gasparitag {% extends %}
il tag {% extends %} è flessibile
{% extends (mobile_layout) ? "layout_mobile.html.twig" : "layout.html.twig" %}{% extends (mobile_layout) ? "layout_mobile.html.twig" : "layout.html.twig" %}
{% extends custom_layout %}{% extends custom_layout %}
{% extends [custom_layout, mobile_layout] %}{% extends [custom_layout, mobile_layout] %}
{% extends "TtsDemoBundle::layout.html.twig" %}{% extends "TtsDemoBundle::layout.html.twig" %}
http://traintosymfony.com57 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com58 Emanuele Gasparimacro
# src/Tts/DemoBundle/Resources/views/Default/index.html.twig
{% import "TtsDemoBundle::macros/macrosProdotto.html.twig" as macrosProdotto %}
{% for entity in entities %}{{ macroProdotto.printDescrizione(entity) }}
{% endfor %}
# src/Tts/DemoBundle/Resources/views/Default/index.html.twig
{% import "TtsDemoBundle::macros/macrosProdotto.html.twig" as macrosProdotto %}
{% for entity in entities %}{{ macroProdotto.printDescrizione(entity) }}
{% endfor %}
# src/Tts/DemoBundle/Resources/views/macros/macrosProdotto.html.twig
{% macro printDescrizione(prodotto) %}<div> Descrizione prodotto: {{ prodotto.codice }} - {{ prodotto.descrizione | raw }}</div>
{% endmacro %}
# src/Tts/DemoBundle/Resources/views/macros/macrosProdotto.html.twig
{% macro printDescrizione(prodotto) %}<div> Descrizione prodotto: {{ prodotto.codice }} - {{ prodotto.descrizione | raw }}</div>
{% endmacro %}
una macro è una porzione di codice TWIG riutilizzabile
http://traintosymfony.com59 Emanuele Gaspari
Templating
template engine
Template Naming Pattern
dalla action al template
TWIG
sintassi e variabili
filtri e funzioni
strutture di controllo
comparazioni e test
ereditarietà
macro
variabili globali
TEMPLATING
http://traintosymfony.com60 Emanuele Gasparivariabili globali
in ogni template si posso utilizzare delle variabili globalimesse a disposizione da Symfony
app.security security context.app.user oggetto User correnteapp.request oggetto Requestapp.session oggetto Sessionapp.environment environment corrent (dev, prod)app.debug true se in modalità debug
app.security security context.app.user oggetto User correnteapp.request oggetto Requestapp.session oggetto Sessionapp.environment environment corrent (dev, prod)app.debug true se in modalità debug
http://traintosymfony.com61 Emanuele Gasparivariabili globali custom
è anche possibile specificare una variabile nei file di configurazione,e utilizzarla in qualsiasi template TWIG
{{ available_locales | join(“, ”) }}{{ available_locales | join(“, ”) }}
# app/config/config.yml
parameters:available_locales: [it, en]
twig:globals:
available_locales: %available_locales%
# app/config/config.yml
parameters:available_locales: [it, en]
twig:globals:
available_locales: %available_locales%
TRAINTO SYMFONY
Verona, 13•14 Aprile 2013
the frameworkshop
http://traintosymfony.com@TrainToSymfony
Media partner:©Copyright Emanuele Gaspari Castelletti
top related