$pugMarche->round1()
Intro a Symfony2
Cos'e ?
Symfony2 è un insieme di componenti PHP
disaccopiati e coesi utili per la risoluzione di
problemi comuni.
Symfony2 è anche un framework completo
Cos'e ?
Symfony2 è un insieme di componenti PHP
disaccopiati e coesi utili per la risoluzione di
problemi comuni.
Symfony2 è anche un framework completo
# indipendenti da altri componenti
(modificando A non rompo B)
Symfony2 è un insieme di componenti PHP
disaccopiati e coesi utili per la risoluzione di
problemi comuni.
Symfony2 è anche un framework completo
# singola responsabilità (quindi un unico
motivo per essere modificati)
Cos'e ?
Symfony2 non è un framework ma un progetto
Possiamo scegliere se utilizzare alcune
componenti o tutto il full-stack framework
Cos'e ?
Non è un MVC framework
Il core non fornisce la parte del model.
Possiamo decidere se implementarla con un
ORM (Doctrine), o in qualsiasi altro modo
Garantisce la separazione della logica dalla
presentazione
Cos'e ?
Possiamo definire Symfony2 come un HTTP
Request/Response framework
Riceve richieste HTTP da un client, le elabora
ed invia la risposta al server
Può implementare un pattern MVC per
l'organizzazione interna dell'applicazione.
Cos'e ?
E' stato pensato e costruito intorno alle
specifice HTTP
La logica di applicazioni moderne è sempre più
spostata nel browser
Ci serve quindi qualcosa in grado di gestire
richieste e inviare risposte
Cos'e ?
HTTP Request
GET /contact HTTP/1.1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0
(Macintosh)
VERBO
Header
HTTP
HTTP Request / VERBI
HEAD RECUPERA GLI HEADER DELLA
RISORSA( no body )
GET RECUPERA LA RISORSA DAL SERVER
POST CREA LA RISORSA SUL SERVER
PUT MODIFICA LA RISORSA SUL SERVER
DELETE CANCELLA LA RISORSA DAL SERVER
HTTP Response
HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html
Symfony2 e HTTP
SymfonyComponentHttpFoundationRequest
SymfonyComponentHttpFoundationResponse
● Youporn
● Utilizzato per tanti progetti importanti (BBC,
CBS ..)
● Sta diventando la base di molti progetti
Open Source (silex, phpunit, behat, assetic,
doctrine, propel, phpBB, Drupal8, ezPublish)
● Tutto questo utilizzo inevitabilmente migliora
l'interoperabilità tra questi progetti (ci si
avvicina ad uno standard)
● Vasta comunità ( > 1600 bundles sviluppati
solo nell'ultimo anno)
Perchè ?
● Molti eventi e gruppi
● Documentazione molto esaustiva e in
continuo aggiornamento
● Abbraccia la filosofia "don't reinvent the
wheel" fornendo una stretta integrazione
con molti progetti open source
● Abbraccia a pieno PHP 5.3
● Dependency Injection
● Twig Template engine (che verrà utilizzato
da Drupal ed ezPublish)
● Built-in web profiler e console
Perchè ?
● Codice robusto (ben scritto, testato)
● Tag @api (api pubblica stabile)
● introduce l'utilizzo di tool fantastici come
Composer
Perchè ?
Componenti
dependency injection
Form
Translation
Http Kernel
Class Loader
Dom Crawler
Http Foundation
Templating
Event Dispatcher
Console
Routing Security
Validator
tutti mantenuti su repo git
PHP 5.3
namespace SymfonyComponentHttpFoundation;
namespace SymfonyComponentHttpKernel;
namespace PugMarcheRound1Esempi
use SymfonyComponentDependencyInjectionContainerInterface;
use SymfonyComponentDependencyInjectionContainerBuilder;
Rappresentano un modo di creare insiemi di classi, interfacce,
funzioni e costanti legate tra loro dal comune obiettivo di risolvere un
problema
Namespace
Sono stati creati principalmente per risolvere
due problematiche legate alla creazione di
classi e codice riutilizzabile:
1. collisione dei nomi tra le classi (due classi
con lo stesso nome nello stesso progetto)
2. possibilità di creare alias
(PugMarcheRound1EsempiPrimoEsempi
o as Es1), per accorciare i nomi delle
classi e migliolare la leggibilità del codice
Namespace
Grazie ai namespace e alla possibilità di
definire funzioni di autoloading, il codice
diventa indipendente da dove sono definiti i
suoi elementi all'interno del filesystem
PRS-0 definisce lo standard di utilizzo per i
namespace
Autoloading (PSR-0)
In symfony2 viene caricato app/autoload.php che ha il
compito di includere tutti i file src/ e vendor/
Non si avrà mai bisogno di includere file "a mano".
Symfony2 utilizza i namespace e la convezione psr-0 per
auto-includere tutti i file che serviranno
Nome della classe:
PugMarcheRound1HelloBundleControllerHelloController
Percorso:
src/PugMarche/Round1/HelloBundle/Controller/HelloController.php
Autoloading (PSR-0)
Funzione anonima (o lambda) che può
essere definita in qualsiasi momento
Non possiede un nome
Permette di creare una funzione "al volo" e
passarla come parametro in altri costrutti del
linguaggio
Closure
$string = "Hello";
$closure = function($name) use ($string) {
echo "$string $name";
};
$closure("Riccardo");
Prima :
$hello = create_function('$name, $string', 'echo "$string $name"' );
Rispetto ad una funziona anonima, la closure
può accedere a variabili definite al di fuori
della closure stessa
Closure
Quando viene eseguita, nel suo stack di
esecuzione si porta dietro l'ambiente che ha
intorno al punto dove viene definita (ovvero può
referenziare variabili/oggetti che ha intorno e
modificarli - quindi è in grado di modifcare lo
stato di tali oggetti)
Closure
$input = array(1, 2, 3, 4);
$output = array_filter($input, function($v) {
return $v >2;
});
Risultato : array(3, 4)
Utilizzata principalmente all'interno di
chiamate di callback
Closure
Closure
Vantaggi :
● migliore leggibilità rispetto a
create_function
● migliora le prestazioni: compilate durante
la fase di parsing del codice sorgente
(create_function eseguita a run-time non
può essere messa in cache)
Symfony dalla versione 2.1 adotta Composer
per la gestione delle dipendenze (vendors) ed
eventualmente anche per l'installazione. Dalla
versione 2.1 viene utilizzato anche
l'autoloader di composer.
http://symfony.com/download
http://symfony.com/doc/current/book/installation.html
http://getcomposer.org/
Installazione e config.
Download composer e symfony da dentro la cartella del progetto:
curl -s https://getcomposer.org/installer | php
php composer.phar create-project symfony/framework-standard-edition path/
2.1.2
Aggiornare i vendors:
php composer.phar install
Check configurazione:
http://symfony.com/doc/current/book/installation.html#configuration-and-setup
Escludere i vendors dal repository:
.gitignore >> /vendor/
Installazione e config.
Partiamo dalla creazione di un bundle che
conterrà tutto il sorgente della nostra
applicazione
php app/console generate:bundle --
namespace=PugMarche/Round1/HelloBundle
Creazione di una pagina - #1
Viene creata la cartella:
src/PugMarche/Round1/HelloBundle
In app/AppKernel.php viene aggiunto :
public function registerBundles()
{
$bundles = array(
// ...
new PugMarcheRound1HelloBundlePugMarcheHelloBundle(),
);
// ...
return $bundles;
}
Creazione di una pagina - #1
Creazione di un controllore:
// src/PugMarche/Round1/HelloBundle/Controller/HelloController.php
namespace PugMarcheRound1HelloBundleController;
use SymfonyComponentHttpFoundationResponse;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Ciao '.$name.'!</body></html>');
}
}
Ha il compito di elaborare la richiesta che arriva e restituire una risposta. Il
controllore deve sempre tornare un oggetto Response.
Creazione di una pagina - #2
Creazione di un rotte:
// src/PugMarche/Round1/HelloBundle/Controller/HelloController.php
namespace PugMarcheRound1HelloBundleController;
use SymfonyComponentHttpFoundationResponse;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
class HelloController
{
/**
* @Route("/hello/{name}", name="hello_action")
*/
public function indexAction($name)
{
return new Response('<html><body>Ciao '.$name.'!</body></html>');
}
}
Creazione di una pagina - #2
possiamo accedere
all'oggetto Request
Creazione di una rotta:
// app/config/routing.yml
pug_marche_round1_hello:
resource: "@PugMarcheRound1HelloBundle/Controller/"
type: annotation
prefix: /
Definire una rotta significa semplicemente
definire un mapping tra un URL della nostra
applicazione con una action nel controllore
(ovvero con un metodo)
Creazione di una pagina - #2
Template engine
VELOCE
ogni template è compilato in una classe PHP con codice
ottimizzato. L'overhead di compilazione è minimo. La classe
viene eseguita a runtime (app/cache/{env}/twig)
FACILE
Pensato per essere facile da leggere anche da parte dei
grafici (semplice da imparare)
TWIG
Template engine
SICURO
Possibilità di impostare un escape automatico su ogni output;
lo sviluppatore può definire una sandbox per i template dove
l'utente ha un accesso limitato ai tags, filtri, oggetti e funzioni
FLESSIBILE
Possibilità di creare tags, filtri, funzioni, operatori. Ereditarietà
tra i template
TWIG
Esempio di utilizzo del tag "for":
<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% else %}
<li>Nessun utente trovato</li>
{% endfor %}
</ul>
.. altri tags: if, filter, extends .. http://twig.sensiolabs.org/doc/tags/index.html
Esempio di utilizzo filtro "upper":
{{ title | upper }} -> possibilità di creare dei fitri custom
..altri filtri: date, url_encode, json_encode, capitalize .. http://twig.sensiolabs.
org/doc/filters/index.html
TWIG
Creazione di un vista (twig):
// src/PugMarche/Round1/HelloBundle/Controller/HelloController.php
namespace PugMarcheRound1HelloBundleController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyBundleFrameworkBundleControllerController;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
class HelloController extends Controller
{
/**
* @Route("/hello/{name}")
*/
public function indexAction($name)
{
return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
}
}
Creazione di una pagina - #2
Con un template riusciamo a spostare tutta la presentazione (HTML) in un file
separato e di poterlo riutilizzare (o riutilizzare porzioni) per diverse pagine
render() restituisce un oggetto Response popolato con il contenuto del template
dato.
Per utilizzare render() il controllore deve estendere la classe Controller, che
aggiunge degli helper (come render) alla nostra classe
NomeBundle:NomeControllore:NomeTemplate
/percorso/di/NomeBundle/Resources/views/NomeControllore/NomeTemplate
Creazione di una pagina - #2
Il nostro template Twig:
{# src/PugMarche/Round1/HelloBundle/Resources/views/Hello/index.
html.twig #}
{% extends '::base.html.twig' %}
{% block body %}
Ciao {{ name }}!
{% endblock %}
{% %} -> esegue qualcosa
{{ }} -> stampa qualcosa
Creazione di una pagina - #2
Il padre:
{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% block title %}Benvenuto!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
Creazione di una pagina - #2
app : configurazione dell' applicazione
src : tutto il codice php della nostra applicazione
vendor : librerie dei venditori
web : cartella accessibile pubblicamente (contiene i
frontcontroller che eseguono il bootstrap e inviano la
richiesta al kernel; assets)
Struttura cartelle
Link:
http://fabien.potencier.org/article/65/why-symfony
http://fabien.potencier.org/article/49/what-is-symfony2
http://jasoncoffin.com/2011/03/10/cohesion-and-coupling-principles-of-orthogonal-object-orientated-
programming/
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
http://www.phpbestpractices.it/
http://www.phptherightway.com
http://getcomposer.org
http://symfony.com/doc/current/book/index.html
Riferimenti

introduzione a symfony 2

  • 1.
  • 2.
    Cos'e ? Symfony2 èun insieme di componenti PHP disaccopiati e coesi utili per la risoluzione di problemi comuni. Symfony2 è anche un framework completo
  • 3.
    Cos'e ? Symfony2 èun insieme di componenti PHP disaccopiati e coesi utili per la risoluzione di problemi comuni. Symfony2 è anche un framework completo # indipendenti da altri componenti (modificando A non rompo B)
  • 4.
    Symfony2 è uninsieme di componenti PHP disaccopiati e coesi utili per la risoluzione di problemi comuni. Symfony2 è anche un framework completo # singola responsabilità (quindi un unico motivo per essere modificati) Cos'e ?
  • 5.
    Symfony2 non èun framework ma un progetto Possiamo scegliere se utilizzare alcune componenti o tutto il full-stack framework Cos'e ?
  • 6.
    Non è unMVC framework Il core non fornisce la parte del model. Possiamo decidere se implementarla con un ORM (Doctrine), o in qualsiasi altro modo Garantisce la separazione della logica dalla presentazione Cos'e ?
  • 7.
    Possiamo definire Symfony2come un HTTP Request/Response framework Riceve richieste HTTP da un client, le elabora ed invia la risposta al server Può implementare un pattern MVC per l'organizzazione interna dell'applicazione. Cos'e ?
  • 8.
    E' stato pensatoe costruito intorno alle specifice HTTP La logica di applicazioni moderne è sempre più spostata nel browser Ci serve quindi qualcosa in grado di gestire richieste e inviare risposte Cos'e ?
  • 9.
    HTTP Request GET /contactHTTP/1.1 Host: xkcd.com Accept: text/html User-Agent: Mozilla/5.0 (Macintosh) VERBO Header HTTP
  • 10.
    HTTP Request /VERBI HEAD RECUPERA GLI HEADER DELLA RISORSA( no body ) GET RECUPERA LA RISORSA DAL SERVER POST CREA LA RISORSA SUL SERVER PUT MODIFICA LA RISORSA SUL SERVER DELETE CANCELLA LA RISORSA DAL SERVER
  • 11.
    HTTP Response HTTP/1.1 200OK Date: Sat, 02 Apr 2011 21:05:05 GMT Server: lighttpd/1.4.19 Content-Type: text/html
  • 12.
  • 13.
    ● Youporn ● Utilizzatoper tanti progetti importanti (BBC, CBS ..) ● Sta diventando la base di molti progetti Open Source (silex, phpunit, behat, assetic, doctrine, propel, phpBB, Drupal8, ezPublish) ● Tutto questo utilizzo inevitabilmente migliora l'interoperabilità tra questi progetti (ci si avvicina ad uno standard) ● Vasta comunità ( > 1600 bundles sviluppati solo nell'ultimo anno) Perchè ?
  • 14.
    ● Molti eventie gruppi ● Documentazione molto esaustiva e in continuo aggiornamento ● Abbraccia la filosofia "don't reinvent the wheel" fornendo una stretta integrazione con molti progetti open source ● Abbraccia a pieno PHP 5.3 ● Dependency Injection ● Twig Template engine (che verrà utilizzato da Drupal ed ezPublish) ● Built-in web profiler e console Perchè ?
  • 15.
    ● Codice robusto(ben scritto, testato) ● Tag @api (api pubblica stabile) ● introduce l'utilizzo di tool fantastici come Composer Perchè ?
  • 16.
    Componenti dependency injection Form Translation Http Kernel ClassLoader Dom Crawler Http Foundation Templating Event Dispatcher Console Routing Security Validator tutti mantenuti su repo git
  • 17.
  • 18.
    namespace SymfonyComponentHttpFoundation; namespace SymfonyComponentHttpKernel; namespacePugMarcheRound1Esempi use SymfonyComponentDependencyInjectionContainerInterface; use SymfonyComponentDependencyInjectionContainerBuilder; Rappresentano un modo di creare insiemi di classi, interfacce, funzioni e costanti legate tra loro dal comune obiettivo di risolvere un problema Namespace
  • 19.
    Sono stati creatiprincipalmente per risolvere due problematiche legate alla creazione di classi e codice riutilizzabile: 1. collisione dei nomi tra le classi (due classi con lo stesso nome nello stesso progetto) 2. possibilità di creare alias (PugMarcheRound1EsempiPrimoEsempi o as Es1), per accorciare i nomi delle classi e migliolare la leggibilità del codice Namespace
  • 20.
    Grazie ai namespacee alla possibilità di definire funzioni di autoloading, il codice diventa indipendente da dove sono definiti i suoi elementi all'interno del filesystem PRS-0 definisce lo standard di utilizzo per i namespace Autoloading (PSR-0)
  • 21.
    In symfony2 vienecaricato app/autoload.php che ha il compito di includere tutti i file src/ e vendor/ Non si avrà mai bisogno di includere file "a mano". Symfony2 utilizza i namespace e la convezione psr-0 per auto-includere tutti i file che serviranno Nome della classe: PugMarcheRound1HelloBundleControllerHelloController Percorso: src/PugMarche/Round1/HelloBundle/Controller/HelloController.php Autoloading (PSR-0)
  • 22.
    Funzione anonima (olambda) che può essere definita in qualsiasi momento Non possiede un nome Permette di creare una funzione "al volo" e passarla come parametro in altri costrutti del linguaggio Closure
  • 23.
    $string = "Hello"; $closure= function($name) use ($string) { echo "$string $name"; }; $closure("Riccardo"); Prima : $hello = create_function('$name, $string', 'echo "$string $name"' ); Rispetto ad una funziona anonima, la closure può accedere a variabili definite al di fuori della closure stessa Closure
  • 24.
    Quando viene eseguita,nel suo stack di esecuzione si porta dietro l'ambiente che ha intorno al punto dove viene definita (ovvero può referenziare variabili/oggetti che ha intorno e modificarli - quindi è in grado di modifcare lo stato di tali oggetti) Closure
  • 25.
    $input = array(1,2, 3, 4); $output = array_filter($input, function($v) { return $v >2; }); Risultato : array(3, 4) Utilizzata principalmente all'interno di chiamate di callback Closure
  • 26.
    Closure Vantaggi : ● miglioreleggibilità rispetto a create_function ● migliora le prestazioni: compilate durante la fase di parsing del codice sorgente (create_function eseguita a run-time non può essere messa in cache)
  • 27.
    Symfony dalla versione2.1 adotta Composer per la gestione delle dipendenze (vendors) ed eventualmente anche per l'installazione. Dalla versione 2.1 viene utilizzato anche l'autoloader di composer. http://symfony.com/download http://symfony.com/doc/current/book/installation.html http://getcomposer.org/ Installazione e config.
  • 28.
    Download composer esymfony da dentro la cartella del progetto: curl -s https://getcomposer.org/installer | php php composer.phar create-project symfony/framework-standard-edition path/ 2.1.2 Aggiornare i vendors: php composer.phar install Check configurazione: http://symfony.com/doc/current/book/installation.html#configuration-and-setup Escludere i vendors dal repository: .gitignore >> /vendor/ Installazione e config.
  • 29.
    Partiamo dalla creazionedi un bundle che conterrà tutto il sorgente della nostra applicazione php app/console generate:bundle -- namespace=PugMarche/Round1/HelloBundle Creazione di una pagina - #1
  • 30.
    Viene creata lacartella: src/PugMarche/Round1/HelloBundle In app/AppKernel.php viene aggiunto : public function registerBundles() { $bundles = array( // ... new PugMarcheRound1HelloBundlePugMarcheHelloBundle(), ); // ... return $bundles; } Creazione di una pagina - #1
  • 31.
    Creazione di uncontrollore: // src/PugMarche/Round1/HelloBundle/Controller/HelloController.php namespace PugMarcheRound1HelloBundleController; use SymfonyComponentHttpFoundationResponse; class HelloController { public function indexAction($name) { return new Response('<html><body>Ciao '.$name.'!</body></html>'); } } Ha il compito di elaborare la richiesta che arriva e restituire una risposta. Il controllore deve sempre tornare un oggetto Response. Creazione di una pagina - #2
  • 32.
    Creazione di unrotte: // src/PugMarche/Round1/HelloBundle/Controller/HelloController.php namespace PugMarcheRound1HelloBundleController; use SymfonyComponentHttpFoundationResponse; use SensioBundleFrameworkExtraBundleConfigurationRoute; class HelloController { /** * @Route("/hello/{name}", name="hello_action") */ public function indexAction($name) { return new Response('<html><body>Ciao '.$name.'!</body></html>'); } } Creazione di una pagina - #2 possiamo accedere all'oggetto Request
  • 33.
    Creazione di unarotta: // app/config/routing.yml pug_marche_round1_hello: resource: "@PugMarcheRound1HelloBundle/Controller/" type: annotation prefix: / Definire una rotta significa semplicemente definire un mapping tra un URL della nostra applicazione con una action nel controllore (ovvero con un metodo) Creazione di una pagina - #2
  • 34.
    Template engine VELOCE ogni templateè compilato in una classe PHP con codice ottimizzato. L'overhead di compilazione è minimo. La classe viene eseguita a runtime (app/cache/{env}/twig) FACILE Pensato per essere facile da leggere anche da parte dei grafici (semplice da imparare) TWIG
  • 35.
    Template engine SICURO Possibilità diimpostare un escape automatico su ogni output; lo sviluppatore può definire una sandbox per i template dove l'utente ha un accesso limitato ai tags, filtri, oggetti e funzioni FLESSIBILE Possibilità di creare tags, filtri, funzioni, operatori. Ereditarietà tra i template TWIG
  • 36.
    Esempio di utilizzodel tag "for": <ul> {% for user in users %} <li>{{ user.username }}</li> {% else %} <li>Nessun utente trovato</li> {% endfor %} </ul> .. altri tags: if, filter, extends .. http://twig.sensiolabs.org/doc/tags/index.html Esempio di utilizzo filtro "upper": {{ title | upper }} -> possibilità di creare dei fitri custom ..altri filtri: date, url_encode, json_encode, capitalize .. http://twig.sensiolabs. org/doc/filters/index.html TWIG
  • 37.
    Creazione di unvista (twig): // src/PugMarche/Round1/HelloBundle/Controller/HelloController.php namespace PugMarcheRound1HelloBundleController; use SymfonyComponentHttpFoundationResponse; use SymfonyBundleFrameworkBundleControllerController; use SensioBundleFrameworkExtraBundleConfigurationRoute; class HelloController extends Controller { /** * @Route("/hello/{name}") */ public function indexAction($name) { return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name)); } } Creazione di una pagina - #2
  • 38.
    Con un templateriusciamo a spostare tutta la presentazione (HTML) in un file separato e di poterlo riutilizzare (o riutilizzare porzioni) per diverse pagine render() restituisce un oggetto Response popolato con il contenuto del template dato. Per utilizzare render() il controllore deve estendere la classe Controller, che aggiunge degli helper (come render) alla nostra classe NomeBundle:NomeControllore:NomeTemplate /percorso/di/NomeBundle/Resources/views/NomeControllore/NomeTemplate Creazione di una pagina - #2
  • 39.
    Il nostro templateTwig: {# src/PugMarche/Round1/HelloBundle/Resources/views/Hello/index. html.twig #} {% extends '::base.html.twig' %} {% block body %} Ciao {{ name }}! {% endblock %} {% %} -> esegue qualcosa {{ }} -> stampa qualcosa Creazione di una pagina - #2
  • 40.
    Il padre: {# app/Resources/views/base.html.twig#} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Benvenuto!{% endblock %}</title> {% block stylesheets %}{% endblock %} <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %}{% endblock %} </body> </html> Creazione di una pagina - #2
  • 41.
    app : configurazionedell' applicazione src : tutto il codice php della nostra applicazione vendor : librerie dei venditori web : cartella accessibile pubblicamente (contiene i frontcontroller che eseguono il bootstrap e inviano la richiesta al kernel; assets) Struttura cartelle
  • 42.