You are on page 1of 64

Universit` degli Studi di Verona a

` EX-FACOLTA DI SCIENZE MATEMATICHE, FISICHE E NATURALI Corso di Laurea in Informatica

Tesi di laurea triennale

Web Application Frameworks: comparazione di strumenti per lo sviluppo di applicazioni web allo stato dellarte

Candidato:

Relatore:

Alessio Bogon

Luca Vigano

Anno Accademico 20112012

Indice
1 Web Application Framework 1.1 Funzionalit` . . . . . . . . . . . . . . . a 1.1.1 Model View Controller . . . . . 1.1.2 Database e ORM . . . . . . . . 1.1.3 Representational State Transfer 1.1.4 Sicurezza . . . . . . . . . . . . 1.1.5 Templating . . . . . . . . . . . 1.1.6 Cache . . . . . . . . . . . . . . 2 CodeIgniter 2.1 Struttura . 2.2 URL . . . . 2.3 Controllers 2.4 View . . . . 2.5 Models . . . 2.6 Helpers . . 2.7 Conclusioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 6 6 6 7 8 8 8

11 . . 11 . 12 . 12 . 13 . 15 . 16 . 17 19 . 20 . 20 . 23 . 24 . 27 . . 31 . 32 33 . 33 . 34 . 37 . 39 . . 41 . 43 . 44 . 47 . . . . . . . 49 49 50 53 54 56 60 64

3 Laravel 3.1 Struttura . . . 3.2 Routing . . . . 3.3 Controllers . . 3.4 Views . . . . . 3.5 Models . . . . . 3.6 Migrazioni . . . 3.7 Considerazioni 4 Ruby on Rails 4.1 Struttura . 4.2 Routing . . 4.3 Controllers 4.4 Views . . . 4.5 Modelli . . 4.6 Migrazioni . 4.7 Scaolding . 4.8 Conclusioni . . . . . . . . . . . . . . . .

5 Django 5.1 Struttura . . . . . . . . . . . . 5.2 Routing . . . . . . . . . . . . . 5.3 View layer (Controller) . . . . . 5.4 Template . . . . . . . . . . . . 5.5 Models . . . . . . . . . . . . . . 5.6 Interfaccia di amministrazione . 5.7 Conclusioni . . . . . . . . . . .

INDICE

Capitolo 1

Web Application Framework


Il World Wide Web inizialmente era un sistema di ipertesti statici accessibili via internet. Le modiche alle pagine dovevano essere fatte manualmente dagli autori, mettendo mano direttamente al codice della pagina. Per poter avere contenuti dinamici che non richiedevano la modica manuale delle pagine da parte di un amministratore, venne introdotto lo standard Common Gateway Interface (CGI). Questo permetteva di interfacciarsi a programmi eseguiti sul web server e poter cos` servire contenuti dinamici, senza dover mettere mano ogni volta al codice. Nacquero anche linguaggi pensati appositamente per il web, come ColdFusion, PHP e Active Server Pages (ASP). Nonostante questi linguaggi abbiano molte librerie utili a semplicare la vita ai programmatori nelle azioni pi` comuni, quasi nessuno mette a disposizione funzioni utili per u facilitare la scrittura di applicazioni web, come ad esempio la possibilit` di generare HTML. a Nei primi anni 2000, a seguito della diusione esponenziale di Internet, si avvert` la necessit` di a avere strumenti pi` potenti rispetto al puro linguaggio di programmazione e quindi cominciarono ad u apparire strumenti per lo sviluppo di applicazioni web, ossia i Web Application Framework (WAF). Per capire meglio la necessit` di questi framework, mettiamoci nei panni di un programmatore a a cui viene commissionata una semplice applicazione web: un sistema che permette agli utenti di inserire articoli in un blog. Il primo passo ` quello di scegliere il linguaggio di programmazione da utilizzare e di conseguenza e trovare unarchitettura su cui far girare lapplicazione. Le scelte sono molteplici, ma supponiamo di scegliere PHP come linguaggio e una piattaforma AMP (Apache, MySql, PHP su Linux, Mac OS o Windows). A questo punto deniamo le tabelle da inserire nel database: in questo caso una semplice tabella Posts che contiene il nome dellutente, il titolo e il testo sar` suciente. Successivamente a creiamo una pagina index.php che esegue le seguenti operazioni: 1. si connette al database 2. esegue una query del tipo SELECT * FROM Posts 3. genera lHTML della pagina utilizzando i dati ottenuti Ora ` necessario creare una pagina di inserimento di un nuovo Post, che chiamiamo new post.php. e Questa sar` una pagina statica, ovvero che non varia a seconda dei contenuti del database, che a include un form di inserimento: <form method="POST" action="insert_post.php"> ... </form> Come ultima cosa creiamo la pagina insert post.php, la quale prende i parametri passati ed esegue una query di tipo insert nel database. Incontriamo gi` un primo problema: la stringa a di connessione al database ` presente nella pagina index.php, ma non in questa. Da buoni e programmatori quali siamo, decidiamo di fattorizzare la parte di codice che riguarda la connessione al database in un altro le, inc/connect.php. 5

CAPITOLO 1. WEB APPLICATION FRAMEWORK

Il sistema ora funziona, quindi creiamo lo stile e carichiamo il tutto sul server. Il committente a questo punto per` esige la possibilit` di modicare ed eliminare questi articoli. o a Per farlo dobbiamo creare altre pagine PHP, ognuna con lo stile uguale ripreso dalla index.php. Carichiamo sul server la nuova versione e tempo pochi giorni il committente vorr` modicare la a graca dellapplicazione. Questo si traduce nel modicare ogni singola pagina creata e gi` qui ci si a accorge che un sistema progettato in questo modo non ` facilmente mantenibile nel tempo. Inoltre e se dovessimo cambiare un campo della tabella Posts, ogni pagina scritta dovr` subire una modica a apposita. Se poi pensiamo alla possibilit` di gestire altre tabelle collegate a questa, il tutto diventa a pressoch impossibile da gestire e questo approccio risulta fallimentare. e ` E qui che vengono in aiuto i framework di sviluppo, che permettono, tra le altre cose, di mantenere la logica dellapplicazione separata dalla parte di visualizzazione, di modularizzare il progetto, di gestire i record delle tabelle come oggetti, . . .

1.1

Funzionalit` a

Lo scopo di un framework ` quello di alleggerire il carico di lavoro destinato alla scrittura di codice e per le attivit` pi` comuni di unapplicazione. Molti di questi, ad esempio, forniscono delle librerie a u per laccesso al Database Management System (DBMS), ed alcuni si spingono oltre, permettendo di gestire il database come un sistema di persistenza per gli oggetti deniti nellapplicazione, utilizzando al tecnica Object-Relational Mapping (ORM), la quale permette di associare un oggetto ad un record di una tabella e manipolare quindi i dati molto pi` facilmente. u

1.1.1

Model View Controller

La maggior parte dei WAF implementa unarchitettura Model View Controller (MVC), la quale individua nel software le tre macro aree da cui il nome deriva. Questo approccio risulta vincente, in quanto permette di modularizzare il codice, promuoverne il riutilizzo e rende quindi indipendente il backend dal frontend. Lo sviluppatore, organizzando il codice secondo questo schema, potr` concentrarsi su un problema a specico e avere la (quasi) certezza che ogni intervento rimanga circoscritto al blocco di codice di cui si sta occupando, lasciando intatti gli altri. Se pensiamo poi ad un progetto di grandi dimensioni, in cui presumibilmente ogni parte sar` creata e mantenuta da persone diverse, diventa evidente a come la divisione logica del codice in zone distinte aumenti lecienza complessiva. Un software che implementa MVC ` cos` suddiviso: e Model contiene la cosiddetta business logic, ovvero la logica vera e propria dellapplicazione. View ` la parte che si occupa di presentare i dati allutente. e e Controller ` la parte che riceve linput dallutente, sceglie lazione da intraprendere, ottiene i dati dal Model e presenta la pagina generata dalla View allutente.

1.1.2

Database e ORM

Uno dei problemi pi` ricorrenti durante lo sviluppo di applicazioni web riguarda laccesso ai dati u salvati nel database. Lapproccio pi` semplice per interrogare il database consiste nello scrivere la u stringa di connessione e la query SQL direttamente nel codice e chiamare la funzione che esegue la query; una volta ottenuto il risultato lo si visualizza nella pagina. Questo porta per` ad avere o codice dicilmente mantenibile nel tempo, in quanto una modica alla struttura di una tabella database degenera nella modica di tutte le query che la manipolano. Molti tra i framework web mettono a disposizione API per gestire in modo migliore il database. Infatti vedremo che possiamo denire i parametri di congurazione in un le dedicato ed eseguire query utilizzando dei query builder, ossia classi che costruiscono la query per noi. Ad esempio, la seguente query: -- Ottiene i clienti e il totale speso per ognuno -- che hanno il nome che comincia per A e che

` 1.1. FUNZIONALITA -- hanno fatto ordini per piu di 1200 Euro SELECT Costumer, SUM(OrderPrice) FROM Orders WHERE Costumer LIKE A% GROUP BY Costumer HAVING SUM(OrderPrice)>1200 ORDER BY Costumer pu` essere scritta in CodeIgniter in questa forma o $this->db->select([Costumer, SUM(OrderPrice)]) ->from(Orders) ->like(Costumer, A, after) ->group_by(Costumer) ->having(SUM(OrderPrice) >, 1200) ->order_by(Costumer, asc);

Molti framework permettono invece di creare unassociazione classi-tabelle (Object-Relational Mapping). Questa tecnica favorisce lintegrazione tra software orientato agli oggetti e Relational Database Management System (RDBMS). LORM fornisce la persistenza degli oggetti nel database astraendo le caratteristiche implementative dello specico RDBMS utilizzato. Ad esempio, per creare un nuovo record di una tabella Users utilizzando Ruby on Rails scriveremo qualcosa come: user = User.create( name: "Alessio", surname: "Bogon", email: "alessio.bogon@gmail.com" ); In una sola riga abbiamo creato un oggetto User e salvato nel database. In molti framework le classi che implementano lORM permettono anche di gestire la validazione, ovvero controllare se un oggetto ha i requisiti per essere salvato nel database.

1.1.3

Representational State Transfer

Molti WAF utilizzano alcuni principi dellarchitettura RESTful per la gestione degli oggetti del dominio applicativo. Questo tipo di approccio permette di dare una semantica migliore alle URL e renderle pi` leggibili. u Il protocollo HTTP ` orientato attorno ai metodi e alle risorse: in una richiesta HTTP il metodo e denisce lazione da intraprendere su una certa risorsa. Ad esempio, una richiesta del tipo GET /news indica la volont` da parte del client di ottenere (GET) la risorsa /news dal server; una a richiesta come DELETE /news/32 invece vuole eliminare (DELETE) la risorsa /news/32. Fino a poco tempo fa venivano utilizzati solamente due dei metodi deniti nello standard HTTP, GET e POST. In realt` ne esistono altri, come PUT e DELETE, che vengono utilizzati dai servizi a RESTful. Vediamo un esempio: immaginiamo di dover gestire un database di utenti. Utilizzando un approccio non RESTful ci si pu` interfacciare con delle URL del tipo: o /user_create /user?id=xxx /user_edit?id=xxx /user_delete?id=xxx Abbiamo utilizzato ben quattro risorse, quando in realt` la risorsa ` una: user. Inoltre per a e aggiornare un utente la richiesta da inviare ` POST /user edit?id=xxx, mentre per eliminarlo ` e e GET /user delete?id=xxx. Queste richieste non utilizzano una semantica ben denita. Utilizzando RESTful invece esponiamo solo quella che ` la vera risorsa: e

8 /user/xxx

CAPITOLO 1. WEB APPLICATION FRAMEWORK

Con questultimo approccio si pu` creare un utente semplicemente mandando una richiesta o POST /user con i dati dellutente da creare. Se invece si vogliono ottenere le informazioni riguardo ad un utente baster` inviare una richiesta GET /user/xxx; mentre se si vuole cancellare lutente a baster` mandare una richiesta DELETE /user/xxx. a In programmazione, esiste un acronimo, CRUD, che si riferisce alle quattro operazioni di base sui dati persistenti, ovvero Create, Read, Update e Delete. Per poter avere un software completo dal punto di vista della manipolazione dei dati persistenti, ` necessario implementare almeno queste e quattro funzionalit`. Lo stile RESTful si integra perfettamente con queste operazioni. a La tabella che segue riassume il comportamento dellapplicazione a seconda della richiesta HTTP, a seconda del metodo e della risorsa indicata: HTTP Verb Path Azione Utilizzo GET /users index Visualizza la lista degli utenti GET /users/new new Visualizza il form HTML per la creazione di un utente POST /users create Crea un nuovo utente GET /users/xxx show Visualizza un utente specico GET /users/xxx/edit edit Visualizza il form HTML per la modica dellutente specicato PUT /users/xxx update Aggiorna lutente specicato DELETE /users/xxx destroy Elimina lutente specicato Notiamo come il pattern RESTful semplichi enormemente la struttura delle URL e ne aumenti lespressivit`. a

1.1.4

Sicurezza

La maggior parte dei framework mette a disposizione classi per gestire al meglio la sicurezza. Le principali minacce per le applicazioni web includono il bypass del controllo dellaccesso, la lettura o modica di dati sensibili, limpossessarsi dellaccount di un utente, il defacing del sito stesso, . . . Questo ` possibile sfruttando vulnerabilit` come SQL Injection, Cross Site Scripting (XSS), e a Cross-Site Request Forgery (CSRF), . . . Per prevenire questi attacchi lo sviluppatore deve conoscere le debolezze e studiare le soluzioni per non subire attacchi. I framework sono solo strumenti, non forniscono una sicurezza di tipo plug-n-play: bisogna conoscerne le potenzialit` e le debolezze per a avere unapplicazione considerata sicura.

1.1.5

Templating

Le pagine dinamiche solitamente consistono in una parte statica (HTML) e in una dinamica, ovvero codice server-side che genera HTML. Molti framework rendono disponibile un template engine, un sistema che facilita linserimento di contenuti dinamici allinterno della pagina. Ad esempio, se volessimo costruire un elenco non ordinato a partire da un array di item con la sintassi che mette a disposizione Laravel, il risultato sarebbe il seguente: <ul> @foreach($items as $item) <li>{{ $item }}</li> @endforeach </ul> Questo ` solo un esempio banale; molti template engine permettono di fare cose molto pi` complesse, e u ma lo vedremo in seguito.

1.1.6

Cache

La quasi totalit` dei framework mette a disposizione uno o pi` meccanismi di cache, ovvero la a u possibilit` di saltare alcuni passaggi (come laccesso al database o il rendering del template) durante a

` 1.1. FUNZIONALITA

la generazione della pagina richiesta dal client. Infatti molte richieste possono essere soddisfatte ripresentando una pagina gi` compilata precedentemente, nel caso in cui i contenuti non siano a cambiati. Questo meccanismo comporta un notevole aumento delle prestazioni dellapplicazione, soprattutto nelle situazioni in cui il carico di lavoro ` molto pesante. e In questo documento vedremo i pro e i contro di alcuni framework che lavorano su PHP, Ruby e Python, utilizzando esempi pratici per comprenderne al meglio le funzionalit`. a

10

CAPITOLO 1. WEB APPLICATION FRAMEWORK

Capitolo 2

CodeIgniter
CodeIgniter ` un WAF open source pensato per aiutare gli sviluppatori a creare applicazioni in e modo pi` eciente rispetto allo scriverle da zero, mettendo a disposizione un ricco insieme di librerie u per le operazioni pi` comuni, come laccesso al database, la gestione delle sessioni, la gestione del u lesystem . . . Esso si basa sul pattern MVC, che permette di separare la logica dellapplicazione dalla presentazione. In realt` lapproccio a MVC ` solo consigliato: infatti possiamo scegliere di scrivere a e lapplicazione ignorando del tutto il Model. CodeIgniter ` il pi` spartano tra i framework che andremo a considerare, ma questo gli e u permette di essere facilmente comprensibile e non richiede molto tempo per poter essere utilizzato.

2.1

Struttura

Diamo uno sguardo ora alla struttura di un progetto basato su CodeIgniter, menzionando solo le parti importanti: index.php Questo ` il le su cui verranno indirizzate tutte le richieste: esso inizializza il framework e passa e il controllo a system/core/CodeIgniter.php, il quale sceglier` lazione da intraprendere per a rispondere al client. Application Questa ` la cartella di lavoro per lo sviluppatore. e cong Questa cartella contiene tutti i le di congurazione del framework, come ad esempio i dati per la connessione al database, le route congurate, i moduli da caricare, . . . controllers Questa cartella contiene le classi dei controller , ovvero quelle che manipolano i dati dei modelli e chiamano la view con i dati corretti. models Questa cartella contiene le classi dei modelli. views Questa cartella contiene i le che generano il codice HTML da restituire al client. ... system Questa cartella contiene i sorgenti del framework vero e proprio. Solitamente non ` necessario e accedere o modicare i le in questa cartella, ma pu` risultare utile nel caso in cui si voglia o conoscere il funzionamento vero e proprio del sistema o per riscriverne alcune parti. ... 11

12

CAPITOLO 2. CODEIGNITER

2.2

URL

Gli URL (Uniform Resource Locator) in CodeIgniter sono pensati per essere comprensibili per gli utenti e per i crowler. Ad esempio, si intuisce che lo URL http://example.com/news/article/classifica-frameworks individua una articolo intitolato Classica framworks presente nelle news. Pi` in generale, gli URL tendono ad assumere la seguente forma: u http://example.com/controller/metodo/parametro1/parametro2/.../parametroN Per poter associare le URL ai controller dobbiamo agire sul le di route, situato in application/config/routes.php In questo le infatti deniamo come vanno gestite le richieste che arrivano al web server. Le varie route sono denite in un array associativo, $route, in cui le chiavi corrispondono alla stringa della URI su cui fare il match, e i valori sono le destinazioni, che possono essere ` controller , funzioni di controller o funzioni di controller con parametri. E possibile inserire anche espressioni regolari allinterno della chiave, e i parametri catturati possono essere passati alle funzioni richiamate. Esistono anche due route riservate, default controller e 404 override: la prima permette di denire come verr` gestita la richiesta a /, la seconda invece denisce il comportamento nel a caso in cui la richiesta non trova un match tra le route denite. Vediamo un esempio. Per abilitare il controller News e permettergli di gestire le richieste, ci baster` inserire la riga a $route[news] = news; Possiamo denire route pi` complesse: u $route[news/(:any)] = news/view/$1; Questa route cattura le richieste del tipo GET /news/31 e delega la gestione alla funzione view del controller News, passando come primo parametro quello catturato, nel nostro caso 31. Per rendere il controller Blog quello di default inseriremo la seguente riga: $route[default_controller] = blog;

2.3

Controllers

I controller sono il cuore dellapplicazione, infatti gestiscono il comportamento dellapplicazione allarrivo di una richiesta HTTP. In CogeIgniter questi vanno salvati nella directory application/controllers/. I controller devono estendere la classe CI Controller di CodeIgniter, e denire delle funzioni pubbliche che verranno chiamate dal framework per gestire le richieste. Supponiamo ora di avere un controller Blog, con un metodo per visualizzare lindice dei Post. application/controllers/blog.php <?php class Blog extends CI_Controller { public function index() { echo List of all the posts; } } Visitando la pagina http://example.com/blog/ verr` invocato automaticamente il metodo a index(), quello di default, della classe Blog che abbiamo appena creato. La pagina visualizzata conterr` semplicemente il testo a

2.4. VIEW List of all the posts Aggiungiamo ora della funzionalit` al nostro codice: a application/controllers/blog.php <?php class Blog extends CI_Controller { public function index() { echo List of all the posts; } public function comments() { echo List of all the comments; } } Ora ` possibile accedere alla pagina http://example.com/blog/comments, che mostrer` e a List of all the comments Consideriamo ora il seguente controller : application/controllers/news.php <?php class News extends CI_Controller { public function list($year, $month) { echo "List of the news for $month/$year"; } }

13

La funzione list($year, $month) vuole due parametri: questi verranno presi dallo URL richiesta dal client. Per esempio, richiedendo il seguente URL http://example.com/news/list/2013/03 viene richiamato il metodo associando a $year il valore 2013 e a $month il valore 03. La pagina visualizzata sar` a List of the news for 03/2013

2.4

View

Una view ` una semplice pagina web che pu` contenere del contenuto dinamico. Una potenzialit` e o a delle view ` che possono essere incluse in altre view , e questa gerarchia aiuta a mantenere il codice e modulare. Ad esempio, pu` essere utile utilizzare uno stesso form per la modica o linserimento di o un post di un blog, oppure avere delle view per lheader e per il footer, dato che questi elementi sono solitamente gli stessi per tutte le pagine. Le view non sono mai invocate direttamente dal client, ma vengono caricate da un controller . Ricordiamo che in un pattern MVC ` il controller la parte che viene invocata immediatamente; e sar` questo che successivamente delegher` il controllo alla view appropriata. a a Supponiamo di avere la seguente view : application/views/blogview.php <html> <head> <title>My Awesome Blog!</title>

14 </head> <body> <h1>Welcome to my awesome blog</h1> </body> </html>

CAPITOLO 2. CODEIGNITER

Per poterla caricare, il codice da inserire nella funzione del controller sar` della forma: a $this->load->view(name); Cos` facendo verr` caricata la view name.php. Non ` necessario specicare lestensione del le, a e a meno che non sia dierente da .php. Solitamente per caricare le view si lavora sulla funzione $this->load, la quale permette di mantenere in memoria la lista delle view da caricare. Questo rende possibile lutilizzo di view parziali come vedremo. Carichiamo nel controller la view denita: application/controllers/blog.php <?php class Blog extends CI_Controller{ function index() { $this->load->view(blogview); } } Essendo parti dinamiche, le view permettono di inserire dati generati dinamicamente dal controller al loro interno: riscriviamo il nostro controller per avere il titolo e lheading generato dinamicamente. application/controllers/blog.php <?php class Blog extends CI_Controller{ function index() { $data[title] = "Dinamically generated title"; $data[heading] = "My awesome Heading"; $this->load->view(blogview, $data); } } Richiamando la view blogview con il secondo parametro $data, mettiamo a disposizione della nostra view gli oggetti presenti nellarray. Questi verranno esplosi, in modo tale da rendere le chiavi dellarray associativo delle variabili utilizzabili direttamente. La view ora potr` utilizzare le a variabili $title e $heading: application/views/blogview.php <html> <head> <title><? = $title ?> /title> </head> <body> <h1><? = $heading ?> </h1> </body> </html> Ovviamente ` possibile utilizzare qualsiasi istruzione, in quanto le view sono le PHP: possiamo e quindi creare facilmente cicli per riempire tabelle, elenchi puntati, controlli sulla presenza di dati, ... Per caricare pi` view in successione basta richiamare il metodo $this->load->view() in u successione:

2.5. MODELS application/controller/blog.php <?php class Blog extends CI_Controller{ function index() { $data[title] = "Dinamically generated title"; $data[heading] = "My awesome Heading"; $this->load->view(header, $data); $this->load->view(content, $data); $this->load->view(footer); } }

15

Questo approccio pu` risultare comodo inizialmente, ma lavorando con siti complessi ci si o accorge che non ` per niente essibile. Infatti lunica cosa che permette di fare ` concatenare view , e e ma ad esempio non permette di denire un layout pi` complesso. u Le view di CodeIgniter, sono le pi` povere tra i framework considerati: vedremo come in u Laravel, ad esempio, queste possono estendere un layout comune, delegare linserimento di contenuti alle view che le estendono, utilizzare un template engine per avere codice pi` pulito, . . . u

2.5

Models

I modelli sono classi pensate per lavorare con le informazioni salvate nel database. Essi contengono il codice necessario a manipolare i dati e ci permettono quindi di astrarci dalla struttura del database. Ad esempio, in un blog un modello potrebbe essere il Post: questa classe conterr` le informazioni a di un messaggio (titolo, testo ed autore) e i metodi per ottenere e salvare queste informazioni nel database. application/models/post.php <?php class Post extends CI_Model{ private $id = null; public $title; public $content; public $author; public __construct($id = null){ super->__construct(); $this->load->database(); if ($id != null) { // Se viene passato un id, allora voglio ottenere // dal database quella riga $row = $this->db->get_where(posts, [id => $id]); $this->id = $id; $this->title = $row->title; $this->content = $row->content; $this->author = $row->author; } } // Salva loggetto corrente nel database public save(){ // Preparo larray da inserire $data = [

16 title => $this->title, content => $this->content, author => $this->author, ];

CAPITOLO 2. CODEIGNITER

if ($this->id != null) { // id != null => record gia esistente => update $this->db->where(id, $this->id); $this->db->update(posts, $data); } else { // id == null => nuovo record => insert $this->db->insert(posts, $data); $this->id = $this->db->insert_id(); } } } La classe appena vista incapsula gli attributi e il comportamento di un Post. Vediamo che le variabili di istanza sono esattamente gli attributi della corrispondente tabella Posts del database. Lunico campo non accessibile direttamente ` lID, il quale una volta settato (nella chiamata al e costruttore o dalla funzione save() nel momento in cui inserisce un nuovo record nel database) non potr` pi` essere modicato. Il costruttore infatti controlla se viene passato o meno lID: in caso a u positivo esegue una query per ottenere anche gli altri campi. Anche il metodo save() esegue questo controllo: se lID ` presente nelloggetto signica che abbiamo gi` ottenuto i dati dal database e e a dobbiamo solamente aggiornare il record, altrimenti stiamo lavorando su un oggetto nuovo che va quindi inserito nella tabella. CodeIgniter praticamente non da alcun aiuto per quanto riguarda la gestione dei record: esso fornisce solo metodi per costruire la query, ma di per s non implementa alcun tipo di ORM, cosa e che invece implementano gli altri framework che analizzeremo.

2.6

Helpers

Uno dei punti di forza di CodeIgniter ` il numero di helper presenti. Questi servono per svolgere e pi` velocemente le operazioni pi` comuni, come inviare email, generare form, lavorare con i cookie, u u gestire i le, ottenere informazioni dallURL, . . . Queste funzioni sono comunque molto basilari, ma possono salvare parecchio tempo in diverse situazioni. Ad esempio, se avessimo bisogno di navigare nel lesystem, esiste un Directory Helper, che crea un array associativo della gerarchia di le e cartelle a partire da un path: $map = directory_map(./mydirectory/); In $map ora abbiamo la struttura della cartella mydirectory/, organizzata in array associativi annidati: Array ( [libraries] => Array ( [0] => benchmark.html [1] => config.html [database] => Array ( [0] => active_record.html [1] => binds.html [2] => configuration.html [3] => connecting.html

2.7. CONCLUSIONI [4] [5] [6] [7] ) [2] [3] [4] [5] [6] [7] [8] [9] ) => => => => => => => => => => => => examples.html fields.html index.html queries.html

17

email.html file_uploading.html image_lib.html input.html language.html loader.html pagination.html uri.html

2.7

Conclusioni

CodeIgniter ` uno dei WAF pi` semplici da imparare, ` molto rapido nelleseguire le operazioni e e u e ha una grande comunit` che lo utilizza. Questo per` lo si paga a caro prezzo: le funzionalit` infatti a o a sono molto ridotte per poter avere tempi rapidi di esecuzione. Le due lacune pi` grandi rispetto u agli altri sono la mancanza di un sistema di templating ecace e di un ORM. Vedremo che grazie a questi due strumenti ` possibile avere una grande potenza espressiva per e la view e di avere una gestione della base di dati molto pi` semplice. u

18

CAPITOLO 2. CODEIGNITER

Capitolo 3

Laravel
Laravel ` un framework relativamente nuovo rispetto ai concorrenti, ma non per questo vale meno. e Lo sviluppatore Tayloer Otwell non trovava un framework PHP che lo soddisfasse no in fondo e quindi ha deciso di crearne uno che utilizzasse a pieno le potenzialit` oerte da PHP 5.3 e che a fosse potente e essibile, mantenendo comunque lintuitivit` e la facilit`. Ad oggi Laravel gode di a a unottima comunit`, e ne uscir` una nuova versione tra poco (Laravel 4) che utilizzer` strumenti a a a come Composer per la gestione dei moduli, portando il framework ai livelli di modularit` di Ruby a on Rails. Laravel ` uno strumento fruibile sia dallo sviluppatore alle prime armi sia dagli utenti pi` esperti e u che richiedono una certa essibilit` e potenza espressiva. a Possiamo riassumerne i punti di forza nei seguenti: Bundles I bundle sono moduli a se stanti che permettono di ampliare le potenzialit`. La comunit` a a di Laravel ` piuttosto grande ed esistono molti bundle per varie esigenze, che possiamo e trovare nel Bundle Repository di Laravel (http://bundles.laravel.com/). Questi possono essere scaricati ed installati manualmente, oppure utilizzando lutility Artisan, che lo far` a automaticamente per noi. Eloquent ORM Laravel mette a disposizione Eloquent, una delle pi` potenti implementazioni u di ActiveRecord su PHP. Potremo infatti manipolare il database usando solamente le classi modelli, e tutti i metodi pi` comuni di accesso e manipolazione sono gi` disponibili. u a Logica dellapplicazione Se in CodeIgniter lapplicazione viene gestita interamente dai controller , in Laravel ` anche possibile gestirla direttamente nel le di routing, nel caso in cui dovessimo e sviluppare unapplicazione non troppo complessa. Questo ` ottimo per poter creare piccoli e siti con poche pagine statiche in poco tempo. ` a Restful Controllers E possibile creare per risorse REST in poco tempo, grazie alle potenzialit` di Laravel. Baster` infatti settare una variabile $restful a true e il gioco ` fatto. a e View Laravel permette di creare view utilizzando metodi piuttosto avanzati. Potremo infatti fattorizzare il layout della nostra applicazione in un le a parte e denire il contenuto delle pagine in view separate. Inoltre avremo a disposizione una quantit` impressionante di helper a per la creazione dellHTML. Migrazioni Le migrazioni permettono di gestire il database come fosse un oggetto sotto controllo di versione. Grazie allutility Artisan si potranno eseguire migrazioni e rollback del database molto facilmente. Unit Testing Lo unit testing ` una parte integrante dello sviluppo di Laravel. Gli stessi svilupe patori di Laravel lo utilizzano per aiutare ad assicurare che i cambiamenti nel codice non generino errori. Inoltre Laravel rende semplice la scrittura dei test da parte degli utenti, che potranno essere eseguiti sempre grazie ad Artisan. 19

20

CAPITOLO 3. LARAVEL

3.1

Struttura

Diamo ora uno sguardo alla struttura di unapplicazione: application Questa ` la cartella di lavoro per lo sviluppatore. e cong Questa cartella contiene tutti i le di congurazione del framework, come ad esempio i dati per la connessione al database, le route congurate, i moduli da caricare, . . . controllers Questa cartella contiene le classi dei controller , ovvero quelle che manipolano i dati dei modelli e chiamano la view con i dati corretti. migrations Questa cartella contiene i le di migrazioni, ovvero classi che descrivono una modica del database. models Questa cartella contiene le classi dei modelli. tests Questa cartella contiene le classi per lo unit testing. views Questa cartella contiene i le che generano il codice HTML che viene restituito al client. routes.php Questo le ` uno dei pi` importanti e denisce le cosiddette route, ovvero il collegamento e u tra la URL invocata dal browser e il controller vero e proprio che dovr` gestire la a richiesta. bundles Qui si potranno inserire i bundle creati da noi o dalla comunit` di Laravel. a laravel Questa cartella contiene il framework vero e proprio. public Questa ` la cartella a cui il server web punta come destinazione per le richieste. Contiene un e le index.php che attiva il framework e contiene anche tutti i le che riguardano la parte statica dellapplicazione, quindi tutti gli assets (fogli di stile, javascript, immagini) e i vari media. storage Questa cartella viene utilizzata come cartella temporanea per salvare le informazioni che devono rimanere persistenti tra le sessioni. In genere non ` necessario consultare questa e cartella. artisan Questo le ` la CLI (Command Line Interface) di Laravel e permette di eettuare molte e operazioni, tra cui migrazioni, unit testing, eseguire task personalizzati, . . . ...

3.2

Routing

Abbiamo gi` accennato le route quando parlavamo di CodeIgniter e abbiamo visto che erano una a parte secondaria del framework. In Laravel invece sono una delle parti fondamentali, in quanto in un singolo le di route pu` risiedere tutta la logica dellapplicazione. Infatti possiamo scegliere o se utilizzare le route per gestire completamente le richieste o se delegarle ai controller appositi.

3.2. ROUTING

21

Inoltre ` possibile avere una modalit` ibrida, ad esempio gestire la visualizzazione delle pagine e a meno complesse o addirittura statiche e delegare la gestione delle pagine pi` dinamiche ai controller . u Questo ` ottimo per piccoli siti che contengono poche pagine e alcune risorse di tipo REST, in e quanto non ` necessario creare controller per ogni pagina o inserire diversi metodi eterogenei in un e solo controller . Le route sono contenute nel le application/routes.php. Per registrare una route solitamente si scrive qualcosa come Route::get(/, function() { return "Hello World!"; }); Questa route gestisce le richieste di tipo GET /, ovvero alla radice del sito e restituir` al browser a una pagina con scritto Hello World! Il metodo statico get() vuole come primo parametro una stringa o un array di stringhe, che rappresentano le URI su cui fare il match, e come secondo parametro lazione da intraprendere. Questa solitamente ` una closure, ovvero una funzione che pu` essere invocata successivamente. e o Ad esempio Route::any(posts, function() { return "Posts!"; }); Questa route gestisce tutte le richieste HTTP (GET, POST, PUT e DELETE) che arrivano a /posts, e restituisce una pagina con scritto Posts! ` E possibile anche inserire variabili nella URL che verranno catturate e gestite: Route::get(posts/(:num), function($id) { return "Post with id: $id"; }); // Esempio: GET posts/13 Route::put(posts/(:num), function($id){ update_post($id); return "Updated post $id"; }); // Esempio: PUT posts/13 Route::get(news/list/(:num)/(:num), function($year, $month){ "List of the news for $month/$year"; }) // Esempio: GET news/list/2013/08 In realt`, i metodi come get(), post(), put(), delete() ed any() non fanno altro che a richiamare una funzione pi` potente, u Router::register($method, $route, $action) che dato un metodo HTTP, una stringa su cui fare il match e lazione da intraprendere, salva in array lassociazione.

22

CAPITOLO 3. LARAVEL

o Route con nome Scrivere nellHTML delle pagine collegamenti alle URL del sito pu` risultare pericoloso, dato che se un giorno dovesse cambiare la URL di una certa risorsa, tutti i link verso questa risulterebbero non funzionanti. Laravel ci permette di dare nomi alle URL, cos` da poter utilizzare queste variabili al posto della stringa vera e propria. In questo modo se una URL dovesse cambiare, tutta lapplicazione risulterebbe consistente con quella nuova. Per dare un nome ad una route, basta aggiungere delle opzioni al momento della registrazione: // Dichiaro una route per la creazione di un utente, chiamandola user_create Route::get(users/new, array(as => user_create, function() { return View::make(user/new); })); // La pagina /users/create esegue un redirect sulla route user_create Route::get(users/create, function(){ return Redirect::to_route(user_create); }); La prima route inoltre delegher` il compito di creare la pagina alla view presente in a application/views/user/new Analizzeremo meglio le view in seguito. Filtri ed eventi In Laravel ` possibile anche denire dei ltri e degli eventi, che verranno attivati e a seconda delle richieste. Ad esempio, ` possibile denire il comportamento nel caso in cui una e richiesta non soddis alcuna route registrata. In questo caso levento che scatta ` 404: e Route::listen(404, function() { return "Error 404: File not found"; }); I ltri possono essere eseguiti prima o dopo lesecuzione di una route. Se un evento di tipo before (da eseguire prima) ritorna un valore, questo sar` considerato la risposta alla richiesta a e la route vera e propria non verr` eseguita. Questo risulta comodo, ad esempio, per gestire a lautenticazione degli utenti. La registrazione di un ltro pu` avvenire nel modo seguente: o Route::filter(blockpage, function() { View::make(blocked); }); Quando attivato questo ltro visualizzer` una view blocked.php. a Vediamo come si pu` collegare ad una route: o Route::get(specialpage, array(before => blocked, function() { return "This page is filtered with blockpage."; })); ` Come abbiamo accennato prima, ` possibile delegare la gestione delle richieste ai controller . E e importante notare che in Laravel tutte le route vanno denite esplicitamente, incluse quelle per i controller . Questo signica che i metodi dei controller che non sono stati registrati non saranno accessibili. Possiamo per` esporre automaticamente tutti i metodi di un controller utilizzando la funzione o di registrazione Route::controller(). Passando come parametro il nome del controller , tutti i suoi metodi che soddisfano certe condizioni, che vedremo dopo, saranno esposti al pubblico. Esiste anche una funzione, Controller::detect(), che restituisce in un array tutti i controller presenti nellapplicazione. Possiamo utilizzarlo per registrare tutti i controller in una sola riga:

3.3. CONTROLLERS Route::controller(Controller::detect());

23

Quindi se ad esempio abbiamo denito il controller Posts, tutte le richieste che arrivano a /posts/... verranno gestite da questo. Una volta che il controller ` stato registrato, la gestione dello URL avviene come in CodeIgniter: e http://example.com/controller/metodo/parametro1/parametro2/.../parametroN

3.3

Controllers

In Laravel la modalit` di funzionamento dei controller ` simile a quella di CodeIgniter, in quanto i a e nomi dei metodi della classe corrispondono, in parte, al segmento corrispondente nello URL. Una particolarit` ` che se registriamo un controller utilizzando Route::controller(), i metodi ae che vogliamo esposti alle richieste web devono essere preceduti da action . In questo modo potremo denire metodi ausiliari e non renderli utilizzabili dallutente. Vediamo un controller di base: application/controllers/posts.php <?php class Posts_Controller extends Base_Controller { public function action_index() { return "List of all the posts"; } } In questo esempio deniamo il metodo action index(), che ` esposto alla parte web ed ` e e anche il metodo di default nel caso in cui non fosse presente nello URL. Quindi visitando la pagina http://example.com/posts/ visualizzeremo la pagina con ununica scritta List of all the posts In Laravel ` buona norma estendere la classe Base controller, la quale a sua volta estende e Controller, ed ` implementata in modo tale da visualizzare una pagina di errore nel caso in cui e venga richiamato un metodo del controller che non esiste. Vediamo limplementazione: application/controllers/base.php <?php class Base_Controller extends Controller { /** * Catch-all method for requests that cant be matched. * * @param string method * @param array parameters * @return Response */ public function __call($method, $parameters) { return Response::error(404); } }

24

CAPITOLO 3. LARAVEL

In PHP infatti la funzione call($method, $parameters) viene invocata automaticamente quando si tenta di accedere ad un metodo che non esiste. Laravel permette di implementare il pattern REST in modo molto semplice; basta aggiungere una propriet` al controller : a class Home_Controller extends Base_Controller { public $restful = true; } In questo modo al posto di utilizzare il presso action come nome delle funzioni si utilizzer` il a nome del metodo HTTP a cui queste dovranno rispondere. Il controller che segue ` un controller RESTful, in cui dichiariamo i metodi che, una volta e implementati, permetteranno di gestire la risorsa Post utilizzando i metodi HTTP GET, POST, PUT, DELETE. application/controllers/posts.php <?php class Posts_Controller extends Base_Controller { public $restful = true; public function get_index($id = null) { // se id != null, visualizza il post selezionato // altrimenti visualizza la lista di tutti i post } public function post_index() { // Crea un nuovo post utilizzando i valori presenti nella richiesta } public function put_index($id) { // Aggiorna il post id utilizzando i valori presenti nella richiesta } public function delete_index($id) { // Elimina il post id } }

3.4

Views

Come abbiamo visto nellintroduzione, le view sono pagine che contengono il codice HTML (o JSON, YAML, . . . ) da restituire al browser. In Laravel vanno salvate nella directory application/views/. Riprendiamo la view di esempio creata in precedenza: application/views/blogview.php <html> <head> <title>My Awesome Blog!</title> </head> <body>

3.4. VIEWS <h1>Welcome to my awesome blog</h1> </body> </html> Per collegare questa view alla homepage del sito, dovremo inserire la seguente route: Route::get(/, function() { return View::make(blogview); });

25

Ovviamente si possono passare dati generati dinamicamente dallapplicazione alle view . Vediamo come creare una view con variabili: application/views/blogview.blade.php <html> <head> <title>{{ $title }}</title> </head> <body> <h1>{{ $heading }}</h1> </body> </html> Le variabili verranno passate in questo modo: application/config/routes.php <?php Route::get(/, function() { $data = array( title => My awesome Blog!, heading => My awesome Heading! ); return View::make(blogview, $data); }); Notiamo che per visualizzare le variabili utilizziamo una sintassi non PHP. Infatti Laravel mette a disposizione Blade, un template engine. Blade rende pi` comodo lo scripting allinterno delle u view ; infatti per stampare delle stringhe baster` richiudere il codice tra doppie parentesi grae. a Inoltre avremo a disposizione delle strutture di controllo molto comode: Cicli Foreach: @foreach ($posts as $post) {{ $post->body }} Author: {{ $post->author }} @endforeach Cicli For Else: @forelse ($posts as $post) {{ $post->body }} Author: {{ $post->author }} @empty There are not posts in the array! @endforelse Cicli While:

26 @while ($something) I am still looping! @endwhile Condizioni: @if ($errors) <p>{{ $errors->all }}</p> @endif @if ($message == success) It was a success! @elseif ( $message == error ) An error occurred. @else Did it work? @endif Condizioni Unless: @unless(Auth::check()) Login @endunless

CAPITOLO 3. LARAVEL

Ma le potenzialit` nellambito delle view non niscono qui. Solitamente le pagine di un sito web a hanno un layout comune, quindi ` consigliabile fattorizzare le parti comuni in un le a parte. In e CodeIgniter abbiamo visto come si possano includere delle view allinterno di altre. Il meccanismo, per`, non ` risultato vincente. o e Vediamo invece cosa possiamo fare con Laravel. Supponiamo di voler creare unapplicazione con due pagine, la homepage e una pagina per essere contattati. Vediamo il le per lhomepage: application/views/home.blade.php @layout(layout/master) @section(title) Homepage! @endsection @section(content) <h1>My homepage!</h1> <p>This is the home page of my site.</p> @endsection e per il layout: application/views/layout/master.blade.php <!DOCTYPE html> <html> <head> <title>@yield(title)</title> <link href="sitestyle.css" type="text/css" rel="stylesheet" /> </head> <body> @yield(content) </body> </html> La comprensione ` abbastanza immediata: nella homepage deniamo il layout da utilizzare e per il rendering delle pagine e deniamo le sezioni che verranno poi incluse. Nel layout invece richiamiamo le sezioni denite.

3.5. MODELS

27

Fantastico! Questo strumento ci permette di scrivere codice molto pi` pulito e semplice da u mantenere. Ma andiamo oltre: supponiamo di voler inserire codice CSS personalizzato nella pagina di contatti. La pagina sar` qualcosa del tipo: a application/views/contacts.blade.php @layout(layout/master) @section(style) @parent <style type="text/css"> .hell { color: red; } </style> @endsection @section(title) Contact me! @endsection @section(content) <h1>Contact me!</h1> <p>Please, send your request to <span class="hell">/dev/null</span></p> @endsection Abbiamo aggiunto una nuova sezione; vediamo di modicare il le di layout in modo la possa utilizzare. application/views/layout/master.blade.php <!DOCTYPE html> <html> <head> <title>@yield(title)</title> @section(style) <link href="sitestyle.css" type="text/css" rel="stylesheet" /> @yield_section </head> <body> @yield(content) </body> </html> In questo modo con il comando @section(style) deniamo la sezione, ne inseriamo il contenuto, e con @yield section la includiamo nella pagina. Questa sezione per` potr` essere o a essere modicata, sovrascritta o lasciata cos` come ` dalla pagina che utilizza il layout. Nella pagina e home infatti non viene dichiarata alcuna sezione style e quindi verr` renderizzata cos` come `; a e tuttavia nella pagina dei contatti la sezione viene modicata, aggiungendo uno stile personalizzato. Notiamo luso del comando @parent, il quale include il codice gi` denito per quella sezione. In a questo modo non perderemo lo stile delle pagine gi` denito. a

3.5

Models

Unaltra delle potenzialit` di questo framework ` Eloquent. Eloquent ` un ORM (Object Relational a e e Mapper), uno strumento per poter gestire le righe di una tabella come semplici oggetti. Riprendiamo lesempio visto in CodeIgniter della classe Post, che utilizza la tabella Posts del database, ma utilizzando le potenzialit` di Eloquent: a

28 application/models/post.php <?php class Post extends Eloquent {}

CAPITOLO 3. LARAVEL

Tutto qui. In questo modo possiamo gi` manipolare le righe del database: a // Creazione di un Post e inserimento nel database // Equivalente SQL: // INSERT INTO Posts (title, content, author) // VALUES (My new post, Short non-boring content, Alessio); $p1 = Post::create(array( title => My new post, content => Short non-boring content, author => Alessio )); echo $p1->id; // 1 // Creazione di un oggetto Post $p2 = new Post; $p2->title = My second post, without content; $p2->author = Alessio; // Salvataggio nel DB $p2->save(); echo $p2->id; // 2 // Tutti i posts // Equivalente SQL: // SELECT * FROM Posts $posts = Post::all(); // Equivalente SQL: // SELECT * FROM Posts // WHERE id = 2 // LIMIT 1 $p = Posts::find(2); // Modifica di un post $p->title = This is still the second post; $p->save(); Dopo aver visto cosa si pu` fare avendo solo denito il nome della classe, sorgono spontanee o delle domande: come fa Eloquent a conoscere il nome della tabella? Come pu` conoscere il nome o degli attributi della tabella e permettere di accederci come variabili distanza? Eloquent fa delle assunzioni per quanto riguarda la struttura del database: ogni tabella ha un attributo id come chiave primaria e il nome di una tabella ` il plurale (in inglese) del nome della e classe. Ovviamente ` possibile trovarsi in situazioni in cui il nome della tabella non corrisponde al e plurale del modello, ad esempio nel caso in cui utilizzassimo una lingua diversa dallinglese. In tal caso basta specicare il nome della tabella da usare: application/models/Post.php <?php class Messaggio extends Eloquent { public static $table = Messaggi; // invece di messaggios }

3.5. MODELS

29

Convention over Conguration Molti framework utilizzano questo paradigma, chiamato Convention over Conguration, il quale prevede una congurazione minima del framework, obbligando a modicare solo quei parametri che dieriscono dalle implementazioni standard. Questo tipo di approccio semplica notevolmente la programmazione soprattutto agli stadi iniziali dello studio di un nuovo framework, senza necessariamente perdere essibilit` o possibilit` di discostarsi dai a a modelli standard. Relazioni Eloquent permette anche di manipolare tabelle con relazioni. Ad esempio, supponiamo di avere una relazione uno a molti tra la tabella Users e la tabella Posts. Gli attributi delle tabelle sono rappresentati nelle seguenti righe: Users CREATE TABLE Users ( id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100) ); Posts CREATE TABLE Posts ( id INT PRIMARY KEY, title VARCHAR(100), content TEXT, author INT REFERENCES(Users.id) ); Quindi avremo che un utente ha molti post e ogni post appartiene ad un solo utente. Dato che non tutti i DBMS supportano le relazioni tra tabelle, queste si possono gestire anche solamente dal lato dellapplicazione, a patto che listanza del database soddis i vincoli di integrit` interrelazionale. a Sar` quindi necessario istruire Eloquent per poter lavorare con le relazioni; le due classi corrispondenti a alle tabelle devono essere cos` denite: application/models/user.php <?php class User extends Eloquent { public function posts() { return $this->has_many(Posts); } } application/models/post.php <?php class Post extends Eloquent { public function user() { return $this->belongs_to(User); } } Potremo quindi manipolare i dati in questo modo: // Ottieni la lista dei post di Alessio: // Equivalente SQL: // SELECT *

30 // FROM Users // WHERE Users.email = alessio.bogon@gmail.com // LIMIT 1; // // SELECT * // FROM Posts // WHERE Posts.author = <id>; $alessio = User::where_email(alessio.bogon@gmail.com); $posts_alessio = $alessio->posts; // Inserisci dei posts per Alessio $posts = array( array( title => A non-significant post, content => With a non -significant content ), array( title => Another post ) ); $alessio->posts()->save($posts);

CAPITOLO 3. LARAVEL

Validazione Qualsiasi applicazione web ha bisogno di validare i dati che vengono dallutente. Ad esempio, un form di registrazione probabilmente ha bisogno di ripetere due volte la password, o che lindirizzo email inserito sia valido e univoco. La validazione pu` essere un processo scomodo, ma o fortunatamente Laravel fornisce una classe Validator che esegue il lavoro sporco. ` Per eseguire la validazione dobbiamo denire prima le regole. E buona norma denirle allinterno del modello di cui dovremo validare i dati di input: application/models/user.php <?php class User extends Eloquent { $rules = array( name => required|max:50, email => required|email|unique:users ); } La regola appena denita vuole che il campo name sia presente e di lunghezza massima 50 caratteri e che il campo email sia presente, di tipo email e univoco allinterno della tabella Users. Esistono tantissimi tipi di regole, ma ovviamente non le vedremo tutte in questo documento. I dati di input presenti nella richiesta si possono ottenere con Input::all(); E inne per validarli creeremo unistanza di Validator a partire dallinput e dalle regole: $validation = Validator::make($input, $rules); if ($validation->fails()) { return $validation->errors; } In $validation sar` presente lo stato della validazione. Nel caso in cui questa fosse fallita, ` a e possibile ottenere gli errori dalla variabile distanza errors.

3.6. MIGRAZIONI

31

3.6

Migrazioni

Con Laravel ` possibile applicare al database i principi del controllo di versione. Infatti le migrazioni e possono essere viste come dei commit della struttura del database. Prima di poter usufruire di questo strumento ` necessario creare la tabella delle migrazioni. e Questo pu` essere fatto in modo indolore utilizzando lutility Artisan da terminale: o php artisan migrate:install Ora possiamo creare la prima migrazione: php artisan migrate:make create_users_table Troveremo in application/migrations/ un nuovo le del tipo 2013_03_08153045_create_users_table.php Gi` il nome del le descrive il momento in cui la migrazione ` stata creata e il nome della migrazione. a e Aprendo il le, troveremo la seguente classe: application/migrations/2013 03 08153045 create users table.php <?php class Create_Users_Table { /** * Make changes to the database. * * @return void */ public function up() { // } /** * Revert the changes to the database. * * @return void */ public function down() { // } } I metodi up() e down() sono chiari: il primo verr` invocato durante lesecuzione della migrazione, a mentre il secondo verr` invocato in caso di rollback. Implementiamoli in modo appropriato: a application/migrations/2013 03 08153045 create users table.php <?php class Create_Users_Table { public function up() { Schema::create(users, function($table) { $table->increments(id); $table->string(name); $table->string(email); $table->timestamps(); });

32 } public function down() { Schema::drop(users); } }

CAPITOLO 3. LARAVEL

Per denire la struttura della tabella utilizziamo lo schema builder di Laravel. Questa classe ci permette di manipolare la struttura delle tabelle: possiamo aggiungere ed eliminare attributi e tabelle, gestire gli indici, le chiavi esterne, . . . Il metodo statico Schema::create() vuole due parametri: il primo ` il nome della tabella, il secondo una closure, in cui inserire gli attributi della e tabella. Infatti questa funzione verr` invocata con un oggetto di classe Table, su cui lavorare per a denire i cambiamenti da apportare alla tabella. Ora ` possibile eseguire la migrazione: e php artisan migrate Nel database ora ` presente la tabella Users con gli attributi richiesti. La funzione timestamps() e di Table inserisce automaticamente due attributi created at e updated at che mantengono la data e lora di creazione e di modica dei record della tabella. Per eseguire il rollback delle migrazioni ` suciente invocare Artisan in questo modo: e php artisan migrate:rollback

3.7

Considerazioni

Laravel ` ancora agli inizi della sua vita se confrontato con i suoi concorrenti (` stato rilasciato per e e la prima volta nel 2011), ma ha gi` molte feature che non tutti riescono a raggiungere. Il fatto di a avere un ORM cos` potente lo rende molto allavanguardia, forse solo Ruby on Rails riesce a essere pi` ecace da questo punto di vista. Inoltre Blade, il template engine, ` molto performante perch u e e compila il codice alla prima richiesta, lo salva in cache e alle successive richieste lo riutilizza. Laravel gode anche di ottima estensibilit`. Infatti ` possibile scaricare e utilizzare i bundles, a e pacchetti scritti dalla comunit` per aggiungere funzionalit` al framework. In questo modo possiamo a a anche aggiungere funzionalit` ad Artisan, lutility da riga di comando, ed avere ad esempio tasks a che generano tabelle a partire dagli argomenti passati ad Artisan. Un punto a svantaggio di Laravel ` la documentazione: sebbene sia ricca di esempi, non permette e ad un programmatore che si approccia per la prima volta al framework di realizzare unapplicazione funzionante. Inoltre non da neanche una visione troppo approfondita delle varie classi del framework: per capire come funzionano tutte le varie classi molte volte bisogna spulciare il codice, che per` ` oe molto ben commentato. Questo framework quindi ` pi` dicile da imparare rispetto agli altri, ma una volta che lo si e u conosce rende lo sviluppo molto meno faticoso.

Capitolo 4

Ruby on Rails
` Rails ` un WAF scritto in Ruby. E stato creato con lintento di rendere la programmazione delle e applicazioni web pi` semplice studiando quello che ogni programmatore ha bisogno per cominciare. u Rails permette di raggiungere lo stesso traguardo scrivendo meno codice rispetto ai suoi concorrenti. Ruby on Rails ` uno spin-o di Bootcamp, un project management tool creto da 37signals. David e Heinemeier Hansson ha estratto le componenti di questo sistema che potevano essere riutilizzate dando vita cos` a Rails nel Luglio del 2004. Rails con il tempo si ` arricchito di moltissime e funzionalit` che lo rendono oggi uno dei pi` migliori strumenti per lo sviluppo di applicazioni web. a u La losoa di Rails include alcuni punti fondamentali: u DRY Dont Repeat Yourself, ovvero se scriviamo lo stesso codice in pi` parti del programma probabilmente stiamo sbagliando qualcosa. Convention Over Conguration Come gi` visto, questa losoa suggerisce di seguire le cona venzioni piuttosto che dover congurare ogni singolo aspetto dellapplicazione. Ad esempio, se utilizziamo un oggetto di classe Utente, ci aspettiamo che nel database la tabella si chiami Utenti. REST Questo pattern ` il migliore per le applicazioni web. Infatti ci permette di organizzare lape plicazione attorno alle risorse, e il tipo di richiesta HTTP determina lazione da intraprendere su di essa. Rails include strumenti che rendono praticamente immediata la creazione delle applicazioni. Abbiamo ad esempio lo strumento di scaolding, che permette la creazione automatica di modelli, view e controller conoscendo solo la struttura della tabella su cui lavorare.

4.1

Struttura

Per creare unapplicazione non ` necessario scaricare alcun le. Basta infatti avere Ruby (e e RubyGem) installato nel sistema e lanciare il comando rails new myapplication Rails generer` tutta la struttura del progetto. Vediamo le parti pi` importanti. a u app Questa cartella contiene i componenti dellapplicazione, come le view , i controller , i modelli. assets In questa directory possiamo inserire i fogli di stile e javascript. Da notare che ` possibile e utilizzare linguaggi diversi dal puro CSS o JavaScript: Rails supporta una grande variet` a di alternative, come SASS, SCSS, LESS, . . . per i fogli di stile e CoeScript, TypeScript, . . . per JavaScript. 33

34

CAPITOLO 4. RUBY ON RAILS helpers Questa cartella contiene gli helper, ovvero codice che non appartiene propriamente a nessuno tra controller , view e modelli ma che questi necessitano per funzionare. models, controllers, view Questa cartelle contengono i tre componenti del pattern MVC.

cong Questa cartella contiene i le di congurazione per i vari componenti, come il database, gli environment, le localizzazioni. Inoltre ` presente il le di route. e db Solitamente in unapplicazione web vogliamo mappare i modelli alle tabelle del database: in questa cartella sono presenti le migrazioni, un le che descrive la struttura del database, un le per inserire valori di test nelle tabelle, . . . doc Ruby ha a disposizione un framework, RubyDoc, che automaticamente genera la documentazione per il codice scritto, come JavaDoc. La documentazione verr` generata in questa a cartella. lib Qui ` possibile inserire le librerie che vogliamo includere. e log In questa cartella troviamo i le di log dellapplicazione. public Tutto i le statici dellapplicazione, come le immagini e i media, vanno inseriti in questa cartella. script Questa cartella mantiene gli script utilizzati per lanciare e gestire i vari strumenti che si usano in Rails, come ad esempio script che lanciano il server o che generano codice (scaolding). tmp Rails utilizza questa cartella per mantenere i le temporanei, come ad esempio la cache per gli asset. vendor Qui possiamo installare librerie di terze parti, come ad esempio driver per connessioni a database diversi da quelli supportati ucialmente da Rails. Gemle, Gemle.lock Questi le permettono di aggiungere e rimuovere le dipendenze dellapplicazione. Come ogni applicazione Ruby, anche Rails ha un suo Gemle.

4.2

Routing

Quando arriva una richiesta da parte di un browser alla nostra applicazione, Rails controlla nel le di route lazione da intraprendere, e poi ne delega la gestione al controller indicato. In un le di route di Rails quindi troveremo soltanto i mapping tra gli URL ed i controller . Un esempio banale di le di route potrebbe essere il seguente: config/routes.rb Myapplication::Application.routes.draw do match "/photos/:id" => "photos#show" end

4.2. ROUTING

35

Questa route intercetta le richieste del tipo GET /photos/15 e delegher` la gestione al metodo a show() del controller Photos, passando come parametro :id il valore 15. Nella stringa da catturare, infatti, possiamo inserire variabili, come gi` abbiamo visto negli altri framework, che a verranno poi passate come argomenti al metodo designato. Nella prima riga del le di route accediamo all modulo Application.routes allinterno del namespace Myapplication, quello della nostra applicazione, e chiamiamo la funzione draw. Il parametro passato a questultima ` il blocco di codice che sta tra do ed end, che ` la congurazione e e delle route vera e propria. Solitamente per congurare route semplici il metodo utilizzato ` match(), e il quale vuole come primo parametro obbligatorio lassociazione tra la stringa su cui fare il match ` e il modulo che la gestisce. E possibile poi passare altri parametri che deniscono meglio il comportamento. Riprendendo lesempio visto in precedenza degli articoli presi per anno e mese: match "/news/list/(:year)/(:month)" => "news#list" Con una richiesta del tipo GET /news/list/2013/03 invochiamo il metodo list() del controller news passando come parametri :year con valore 2013 e :month con valore 03. Notiamo che i valori attuali degli argomenti sono sempre stringe, se vogliamo convertirli in interi dovremo farlo allinterno del controller . e Route con nome Come visto in Laravel, ` possibile creare route con nomi, in modo da poterle riutilizzare dopo: match exit => session#destroy, :as => :logout Questo frammento di codice creer` due funzioni helper, logout path e logout url, le quali a ritorneranno rispettivamente /exit e http://example.com/exit. Una piccola nota sulla sintassi: in Rails ` consigliabile usare i simboli al posto delle stringhe, e dove possibile. Questo perch i simboli vengono convertiti in interi e quindi la gestione diventa pi` e u leggera, soprattutto se vengono usati come chiavi negli array associativi. Un simbolo viene denito con il carattere : seguito da una sequenza alfanumerica di caratteri. Fino a Ruby 1.8 per denire unassociazione chiave-valore era necessario scrivere {:this => syntax, is => so 1.8} Con Ruby 1.9 ` disponibile una sintassi pi` leggera, che permette di non dover aporre il simbolo : e u davanti ai simboli, rendendo pi` uido il codice: u {this: syntax, is: far more comfortable} Dora in avanti verr` utilizzata la nuova sintassi. a Possiamo anche denire route che rispondono a dei metodi HTTP deniti: # Risponde alle richieste di tipo GET /photos/show match photos/show => photos#new, via: :get # Versione abbreviata, mappa automaticamente controller e azione get photos/show # E possibile definire anche piu di un metodo HTTP match photos/show => photos#show, via: [:get, :post] Espressioni regolari Supponiamo ora di voler gestire richieste del tipo GET /photos/A9876, ovvero la richiesta di photo con id A9876. In Rails ` possibile denire vincoli sugli argomenti in e questo modo: # Definiamo unespressione regolare per lid: vocale maiuscola # seguita da quattro cifre match photos/:id => photos#show, constraints: { id: /[A-Z]\d{4}/ }

36

CAPITOLO 4. RUBY ON RAILS

# Versione abbreviata match photos/:id => photos#show, id: /[A-Z]\d{4}/ Notiamo che ad id assegnamo un valore di tipo Regexp. Ruby infatti ha una sintassi apposita per le espressioni regolari, ed ` del tipo /regex/. e Possiamo anche creare route con parametri che catturano pi` segmenti della URL: u match photos/*other => photos#unknown Questa espressione intercetta tutte le URL che iniziano con photos/ e inserisce tutto cio che segue nel parametro :other. Possiamo anche creare route pi` complesse utilizzando sempre le wildcard : u match books/*section/:title => books#show Questa route riconosce richieste come GET /books/some/section/web-application-frameworks e verr` richiamato il metodo books.show(), passando come segue: a books.show(section: some/section, title: web-application-frameworks] Redirect Vediamo inne la gestione dei redirect.

# Redirect semplice da /stories a /posts match /stories => redirect(/posts) # Redirect con parametri match /stories/:name => redirect("/posts/%{name}") # Redirect con parametri e blocchi di codice inline # La seguente route fa il redirect da /stories a /posts # pluralizzando il titolo match /stories/:name => redirect { |params| "/posts/#{params[:name].pluralize}" } RESTful Rails per` utilizza un approccio pi` orientato alle risorse: infatti con questa singola o u riga di codice resources :photos vengono denite implicitamente le 7 route per il controller Photos viste nellintroduzione quando parlavamo delle risorse RESTful. Questa stessa riga inoltre crea degli helper per riferirsi alle route: photos path ritorna /photos photo path(:id) ritorna /photos/:id (quindi chiamando la funzione photo path(15) otterremo la stringa /photos/15) new photo path ritorna /photo/new edit photo path(:id) ritorna /photos/:id/edit Ognuno di questi helper ha anche un corrispondente con susso url, che ritorna lo stesso path ma completo di host, porta e presso. Ad esempio: edit photo url(15) ritorna http://example.com/photos/15/edit

4.3. CONTROLLERS

37

Namespace Supponiamo di voler organizzare un gruppo di controller allinterno dello stesso namespace. Ad esempio, vorremmo avere due controller sotto il namespace Admin: creeremmo questi controller allinterno di app/controllers/admin.rb e inseriremmo le seguenti righe nel le di route: app/controllers/admin.rb namespace :admin do resources :posts, :comments end In questo modo Rails inoltrer` le richieste tipo a GET /admin/posts/new GET /admin/comments/20/edit ai corrispettivi controller allinterno di Admin. Nel caso ne avessimo solo uno allinterno di un namespace potremmo semplicemente passare unulteriore parametro alla funzione resources: resources :posts, module: "admin" Se invece volessimo mappare le richieste come GET /admin/posts ma non utilizzare alcun namespace, la route risulterebbe cos` : resources :posts, path: "/admin/posts"

4.3

Controllers

Una volta che il modulo di routing ha determinato quale controller usare per gestire una richiesta, questo diventa il responsabile per produrre loutput appropriato. Fortunatamente, il modulo Active Controller mette a disposizione classi che implementano la parte pi` grossa del lavoro e utilizza u delle convenzioni che rendono semplice lestensione della classe. Vediamo subito un esempio banale di controller : app/controllers/books controller.rb class BooksController < ApplicationController def index render text: "List of all the books" end end Allinterno del metodo index chiamiamo la funzione render, e passiamo come argomento di chiave :text la stringa che vogliamo visualizzare. In Rails possiamo eseguire il rendering in tre modi: Chiamando la funzione render la quale permette di creare codice automaticamente a partire da semplici oggetti; Chiamando redirect to, la quale redirige la richiesta ad unaltra pagina Lasciare il controllo della richiesta ad una view . Solitamente si sceglie il terzo metodo, preparando prima i dati dinamici da passare alla view e lasciandone il controllo della visualizzazione. Tuttavia lutilizzo di render ` molto comodo nel caso e in cui volessimo renderizzare oggetti in formati come JSON o XML:

38 p = Post.find("22") # Renderizza loggetto in formato JSON render json: p # Renderizza loggetto in formato XML render xml: p

CAPITOLO 4. RUBY ON RAILS

In realt` render chiamer` a sua volta i metodi to json e to xml della classe Post. Possiamo a a anche denire pi` risposte per uno stesso controller , e decidere quale utilizzare a seconda della u estensione della pagina richiesta: app/controllers/books controller.rb class BooksController < ApplicationController def index @books = Books.all renspond_to do |format| format.html format.json { render json: @books } format.xml { render xml: @books } end end end In questo modo, allinterno di index otteniamo la lista dei libri chiamando Books.all, e assegnamo larray in @books. Questa variabile ha il carattere @ davanti al nome, rendendola cos` un variabile distanza. Questa sar` poi accessibile dalla view una volta richiamata. Successivamente chiamiamo a renspond to, e passiamo la funzione anonima che, dato il formato nellargomento format, denisce il comportamento a seconda del fatto che sia HTML, JSON o XML. Chiamando format.html senza parametri, Rails assume che la view da chiamare sia del tipo controller name/action name.html.erb, quindi nella fattispecie app/views/books/index.html.erb Per quanto riguarda format.json e format.xml, passiamo una funzione che chiama render indicando il tipo di risposta e loggetto. In questo modo avremo le seguenti risposte per le richieste: GET /books/index restituir` la pagina renderizzata dalla view situata in a app/views/books/index.html.erb GET /books/index.json restituir` a [ { "created_at": "2013-02-18T11:54:20Z", "id": 1, "author": "Alessio Bogon", "title": "Web Application Frameworks", "updated_at": "2013-02-18T11:54:20Z" }, { "created_at": "2013-02-18T12:23:15Z", "id": 2, "author": "David Kadavy", "title": "Design for hackers",

4.4. VIEWS "updated_at": "2013-02-18T12:23:15Z" } ] GET /books/index.xml restituir` a <?xml version="1.0" encoding="UTF-8"?> <users type="array"> <user> <created-at type="datetime">2013-02-18T11:54:20Z</created-at> <id type="integer">1</id> <author>Alessio Bogon</author> <title>Web Application Frameworks</title> <updated-at type="datetime">2013-02-18T11:54:20Z</updated-at> </user> <user> <created-at type="datetime">2013-02-18T12:23:15Z</created-at> <id type="integer">2</id> <author>David Kadavy</author> <title>Design for hackers</title> <updated-at type="datetime">2013-02-18T12:23:15Z</updated-at> </user> </users> Vedremo in seguito altre potenzialit` dei modelli in Rails. a

39

4.4

Views

Vediamo ora come funzionano le view in Rails. Queste vanno salvate nella directory app/views/, e seguendo gli standard di Rails risulta pi` semplice utilizzarle. Deniamo una semplice view: u app/views/books/index.html.erb <html> <head> <title>Books repository</title> </head> <body> <h1>List of books</h1> </body> </html> Notiamo che questa ` allinterno della cartella books, che ` lo stesso nome del nostro controller . e e Per poterla utilizzare, possiamo utilizzare il seguente codice per il controller : app/controllers/books controller.rb class BooksController < ApplicationController def index end end In questo caso non abbiamo scritto alcuna riga di codice per il metodo index; la regola di Rails ` che se non c` alcun richiamo esplicito al rendering nel metodo del controller , allora la risposta va e e delegata alla view associata, che come abbiamo visto viene determinata automaticamente se non esplicita. Ora vogliamo passare dei parametri alla view: vediamo come il controller BooksController deve risultare:

40

CAPITOLO 4. RUBY ON RAILS

app/controllers/books controller.rb class BooksController < ApplicationController def index @books = Book.all end end Vedremo in seguito i dettagli dei modelli, per ora accontentiamoci di sapere che Book.all restituisce una lista di tutti gli oggetti Book presenti nel database. Anche in questo caso non abbiamo chiamato esplicitamente il rendering della pagina, quindi Rails utilizzer` la pagina di default, che andiamo a ora a modicare come segue: app/views/books/index.html.erb <html> <head> <title>Books repository</title> </head> <body> <h1>Listing Books</h1> <table> <tr> <th>Title</th> <th>Author</th> </tr> <% @books.each do |book| %> <tr> <td><%= book.title %> </td> <td><%= book.author %> </td> </tr> <% end %> </table> </body> </html> Ora facendo una richiesta GET /books/index otterremo la lista dei libri salvati in formato HTML. In questo esempio utilizziamo dei tag speciali di Rails per inserire codice Ruby allinterno della pagina. Analizziamo meglio il frammento:
1 2 3 4 5 6

<% @books.each do |book| %> <tr> <td><%= book.title %> </td> <td><%= book.author %> </td> </tr> <% end %> Nella prima riga chiamiamo il metodo each delloggetto @books creato precedentemente nel controller . Il carattere @, che come visto precedentemente serve dichiarare una variabile distanza, ` e necessario per poter rendere visibile loggetto alla view . A questo metodo passiamo il blocco di codice HTML misto a Ruby compreso tra le righe 2 e 5. Questo codice verr` eseguito e salvato nel a buer della richiesta per ogni libro allinterno della lista @books. I tag <%= e %> presenti alle righe 3 e 4 indicano che vogliamo visualizzare il risultato del codice al loro interno, nel nostro caso il titolo e lautore del libro. In Rails possiamo anche renderizzare view parziali, come visto in altri framework. Creiamo una view parziale che contiene del codice statico per la navigazione: app/views/books/ navigation.html.erb <nav> <ul>

4.5. MODELLI <li> <%= link_to "List", :controller => "books", :action => "index" %> </li> <li> <%= link_to "Create", :controller => "books", :action => "create" %> </li> </ul> </nav>

41

Abbiamo utilizzato uno dei tanti helper di Rails, functionlink to, il quale permette di creare link HTML. E modichiamo la view index.html.erb in questo modo: app/views/books/index.html.erb <html> <head> <title>Books repository</title> </head> <body> <%= render "navigation" %> <h1>Listing Books</h1> <table> <tr> <th>Title</th> <th>Author</th> </tr> <% @books.each do |book| %> <tr> <td><%= book.title %> </td> <td><%= book.author %> </td> </tr> <% end %> </table> </body> </html> Il frammento <%ender navigation %> ` quello che invoca il rendering del le r e views/books/\_navigation.html.erb Vedremo pi` avanti che ` possibile generare tutto il codice per una risorsa automaticamente, u e utilizzando lo scaolding.

4.5

Modelli

Rails mette a disposizione una delle pi` potenti ORM in circolazione, ovvero ActiveRecord. Abbiamo u gi` visto il funzionamento degli ORM in Laravel con Eloquent, il quale si ispira proprio ad a ActiveRecord. Per ottenere oggetti dal database, ActiveRecord provvede molti metodi. Ognuno di questi richiede un argomento che utilizzer` per eseguire query personalizzate sul database senza dover a scrivere alcuna riga di SQL. I modelli in Rails vanno posizionati nella cartella app/models/. Vediamo ora degli esempi per capirne il funzionamento. Deniamo un modello Client, che rappresenta un cliente. app/models/client.rb class Client < ActiveRecord::Base end

42

CAPITOLO 4. RUBY ON RAILS

Rails assume che il nome della tabella corrispondente sia clients, ma ` ovviamente possibile e sovrascrivere questa impostazione. Per ottenere unistanza con un certo id, il codice ` il seguente: e # Ottieni il cliente con id = 25 client = Client.find(25) # => #<Client id:25, first_name: "Guglielmo"> Il metodo find cerca nella tabella il record che ha come primary key quella passata come parametro. Di default lattributo ` id. Il codice appena visto generer` una query del tipo: e a SELECT * FROM clients WHERE (clients.id = 25) LIMIT 1 Nel caso in cui non esistesse alcun Client con id 25 viene lanciata uneccezione RecordNotFound. Vediamo altri modi per ottenere i dati dalla base di dati: # Ottieni i clienti che hanno id 1 o 25 clients = Client.find([1, 23]) # => [#<Client id:1, first_name: "Gianni">, #<Client id:25, first_name: # "Guglielmo">] # Equivalente SQL: # SELECT * FROM clients WHERE (clients.id IN (1,10)) # Ottieni i clienti che hanno eseguito 3 ordini clients = Client.where("orders_count" => 3) clients = Client.where(orders_count: 3) # Versione piu carina # Ottieni i clienti creati ieri clients = Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) # Equivalente sql: # SELECT * FROM clients WHERE (clients.created_at BETWEEN # 2013-02-07 00:00:00 AND 2013-02-08 00:00:00) Nel caso in cui dovessimo utilizzare parametri ottenuti dalla richiesta del browser, ` possibile e utilizzare i cosiddetti prepared statement, ovvero creare query pronte per essere riempite con i valori veri. Ad esempio, supponiamo di avere una richiesta dal browser di tipo POST che richiede i clienti che hanno fatto un certo numero di ordini, e il nome di questo campo sia orders. Ovviamente linput va controllato per prevenire attacchi come SQL Injection. Rails far` questo a lavoro per noi: # Ottieni i clienti che hanno fatto param[:orders] Client.where("orders_count = ?", params[:orders]) ` E possibile ovviamente aggiungere metodi ai nostri modelli. Possiamo ad esempio aggiungere un metodo che incrementa il numero di ordini per un cliente: app/models/client.rb class Client < ActiveRecord::Base def increment_orders self.increment :orders_count end end ` Unaltra cosa su cui vale la pena soermarsi ` linserimento e la modica dei record. E buona e norma dichiarare nel modello gli attributi accessibili, mediante il metodo attr accessible. Questo metodo crea automaticamente i metodi setter e getter per gli attributi specicati, e permette anche di inserire direttamente nel costruttore di un oggetto i valori di inizializzazione. Utilizzando quindi un modello denito in questo modo:

4.6. MIGRAZIONI app/models/client.rb class Client < ActiveRecord::Base attr_accessible :first_name, :orders_count def increment_orders self.increment :orders_count end end possiamo invocare il costruttore con i campi rst name e orders count: # Creazione di un oggetto Client c = Client.new(first_name: "Alessio", orders_count: 0) # Salvataggio nel database c.save # Modifica delloggetto c.increment_orders c.save

43

I campi presenti nella tabella associata al modello che non sono stati dichiarati con attr accessible o che non hanno setter possono comunque essere settati manualmente al di fuori del costruttore. ActiveRecord mette a disposizione un grande numero di opzioni per gestire al meglio i dati sul database. Ovviamente non possiamo vederle tutte, ma ci basta sapere che non avremo praticamente pi` bisogno di scrivere SQL quando lavoriamo in Rails. u

4.6

Migrazioni

In Rails non poteva mancare il supporto alle migrazioni. Abbiamo gi` visto che le migrazioni sono a un modo comodo per modicare la struttura del database in maniera organizzata e controllata. La struttura di una migrazione pu` essere creata invocando da terminale o rails generate migration migration_name ` E possibile passare anche altre opzioni che completeranno automaticamente il le di migrazione, ma per chiarezza vediamo una dichiarazione esplicita: app/db/migrate/201303081515 create products.rb class CreateProducts < ActiveRecord::Migration def up create_table :products do |t| t.string :name t.text :description t.timestamps end end def down drop_table :products end end Questa migrazione aggiunge una tabella chiamata products con due attributi, name di tipo stringa e description di tipo text. Questi tipo verranno poi convertiti nei tipi disponibili nel

44

CAPITOLO 4. RUBY ON RAILS

database vero e proprio, e sar` Rails a scegliere il tipo reale pi` appropriato. Inoltre verr` aggiunto a u a automaticamente un attributo id intero che fa da chiave primaria. Dato che abbiamo chiamato anche la funzione t.timestamps, verranno aggiunti altri due attributi, created at e updated at, che rappresentano la data di creazione e di ultima modica del record. Tutto questo viene fatto allinterno del metodo up, che viene richiamato allesecuzione della migrazione. Il metodo down invece verr` richiamato al rollback, e in questo caso distrugger` la a a tabella. Una migrazione pu` essere creata anche per modicare la struttura di una tabella esistente. o Se ad esempio avessimo una tabella users a cui volessimo aggiungere un attributo che indica se lutente pu` ricevere newsletter, la migrazione sarebbe la seguente: o app/db/migrate/201303081516 add receive newsletter to users.rb class AddReceiveNewsletterToUsers < ActiveRecord::Migration def up change_table :users do |t| t.boolean :newsletter_enabled, :default => false end User.update_all ["newsletter_enabled = ?", true] end def down remove_column :users, :newsletter_enabled end end Questa migrazione aggiunge un attributo booleano newsletter enabled. Vogliamo che per i nuovi utenti questo sia falso, ma gli utenti gi` esistenti li consideriamo abilitati, e quindi chiamiamo a User.update all per abilitare questo ag a quelli esistenti. Le potenzialit` delle migrazioni in Rails sono pressoch le stesse di quelle viste in Laravel. a e Come ultima cosa vediamo come eseguire le migrazioni. Per eseguire tutte le migrazioni non fatte, lanciamo rake db:migrate Per il rollback invece: rake db:rollback ` possibile anche scegliere di quanti passi tornare indietro: e rake db:rollback STEP=2 Nei casi in cui volessimo resettare completamente il database e ripristinare la situazione allultima migrazione: rake db:reset

4.7

Scaolding

Linterfaccia a riga di comando di Rails ci permette di creare codice gi` funzionante in pochi a comandi. Lanciando rails generate visualizziamo un help che ci descrive cosa possiamo generare automaticamente: assets controller

4.7. SCAFFOLDING generator helper integration test mailer migration model observer performance test resource scaold scaold controller session migration task Proviamo ad esempio a lanciare rails generate controller CreditCard open debit credit close create route route route route invoke create create create create create invoke create invoke create invoke create invoke invoke create invoke create app/controllers/credit_card_controller.rb get "credit_card/close" get "credit_card/credit" get "credit_card/debit" get "credit_card/open" erb app/views/credit_card app/views/credit_card/open.html.erb app/views/credit_card/debit.html.erb app/views/credit_card/credit.html.erb app/views/credit_card/close.html.erb test_unit test/functional/credit_card_controller_test.rb helper app/helpers/credit_card_helper.rb test_unit test/unit/helpers/credit_card_helper_test.rb assets coffee app/assets/javascripts/credit_card.js.coffee scss app/assets/stylesheets/credit_card.css.scss

45

In questo modo Rails crea tutta la struttura per un nuovo controller , comprensivo di route, view , unit` di test, helper e assets. Ovviamente il codice generato non ha una vera implementazione, ma a per lo meno si ha da subito una struttura su cui lavorare. Vediamo qualcosa di ancora pi` potente: il generatore scaold. Questo generatore infatti u permette di creare tutta la struttura dellapplicazione attorno ad una risorsa. Immaginiamo di voler creare unapplicazione web per la gestione di una semplice anagraca di utenti:

46

CAPITOLO 4. RUBY ON RAILS

rails generate scaffold user name:string surname:string telephone:string \ address:text email:string username:string:unique password:string invoke create create invoke create create invoke route invoke create invoke create create create create create create invoke create invoke create invoke create invoke invoke create invoke create invoke create active_record db/migrate/20130219154626_create_users.rb app/models/user.rb test_unit test/unit/user_test.rb test/fixtures/users.yml resource_route resources :users scaffold_controller app/controllers/users_controller.rb erb app/views/users app/views/users/index.html.erb app/views/users/edit.html.erb app/views/users/show.html.erb app/views/users/new.html.erb app/views/users/_form.html.erb test_unit test/functional/users_controller_test.rb helper app/helpers/users_helper.rb test_unit test/unit/helpers/users_helper_test.rb assets coffee app/assets/javascripts/users.js.coffee scss app/assets/stylesheets/users.css.scss scss app/assets/stylesheets/scaffolds.css.scss

Cos` facendo abbiamo gi` pronta non solo la struttura, ma anche unimplementazione di base di a tutta lapplicazione. Basta infatti dare il seguente comando per eseguire la migrazione rake db:migrate == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0018s == CreateUsers: migrated (0.0019s) =========================================== e possiamo gi` far partire il server di test: a rails server => Booting WEBrick => Rails 3.2.12 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-02-19 16:56:58] INFO WEBrick 1.3.1 [2013-02-19 16:56:58] INFO ruby 1.9.3 (2013-01-15) [x86_64-darwin12] [2013-02-19 16:56:58] INFO WEBrick::HTTPServer#start: pid=5786 port=3000 Ora possiamo recarci col browser su http://localhost:3000/users e cominciare a inserire e modicare utenti.

4.8. CONCLUSIONI

47

4.8

Conclusioni

Abbiamo visto che Rails permette di scrivere applicazioni in modo molto veloce: ActiveRecord ci permette di utilizzare il database come sistema di persistenza delle classi e lapproccio ` di tipo Convention over Conguration, quindi ci basta personalizzare solo quelle impostazioni che dieriscono da quelle di default il supporto alla generazione di XML e JSON ` integrato nel sistema e il comando rails da terminale ci permette di generare la struttura per gran parte dellapplicazione Ruby poi usa in modo intensivo le closure, ovvero le funzioni anonime, e molto spesso vengono passate come parametro ad altre funzioni. Questo, associato al fatto di essere un linguaggio dinamicamente tipato, permette di scrivere codice chiaro, espressivo e conciso. Il framework poi ` e di per se ben documentato e la comunit` di sviluppatori ` veramente grande. a e Tutto questo per` ha un caro prezzo: le prestazioni. Ruby ` un linguaggio interpretati tra i pi` o e u lenti, soprattutto se paragonato a Python. Inoltre il run-time Ruby utilizza una quantit` di RAM a maggiore rispetto, ad esempio, ad Apache+PHP o Python.

48

CAPITOLO 4. RUBY ON RAILS

Capitolo 5

Django
Django ` un framework basato su Python creato con lintento di rendere semplice la creazione di e siti web complessi che sfruttano pesantemente la base di dati. Esso enfatizza il riutilizzo di codice e la modularit` dei componenti e riprende il principio dont repeat yourself. Django mette anche a a disposizione uninterfaccia amministrativa CRUD generata automaticamente. Django ` stato sviluppato originariamente per gestire siti web per le notizie ed ` stato successie e vamente rilasciato sotto licenza BSD nel Luglio del 2005; il nome originario era Django Reinhardt. Da Giungo 2008 ` la Django Software Foundation a mantenere ed aggiornare il framework. e Alcune delle funzionalit` pi` importanti includono: a u Una robusta API per la gestione del database. Mapping tra oggetti e database relazionale. Possibilit` di installare plugins. a Un sistema di view generiche che permette di saltare la stesura di codice per parti comuni. Un sistema di template basato su tag con funzionalit` avanzate. a Un gestore di URL basate su espressioni regolari. Un sistema middleware per lo sviluppo di funzionalit` aggiuntive; ad esempio, componenti a middleware che forniscono caching, compressione delloutput, normalizzazione delloutput, protezione CSRF e supporto per la sessione sono inclusi nella distribuzione principale di Django. Supporto per internazionalizzazione, incluse traduzioni dellinterfaccia amministrativa in svariate lingue gi` pronte. a Sistema di gestione degli utenti e della loro autenticazione nella applicazione Web. Sistema per la creazione e la validazione di form HTML. In Rails abbiamo visto lo strumento di scaolding per generare gran parte del codice di base e di gestione dei modelli. In Django questo sistema ` ancora pi` potente: con una sola riga di codice e u ` possibile abilitare linterfaccia amministrativa, la quale mette a disposizione uninterfaccia per e gestire tutti i modelli dichiarati.

5.1

Struttura

Per creare un progetto ` necessario avere Django installato. Lanciando il comando e django-admin.py startproject mysite creeremo la struttura di base del nostro progetto. Vediamo di cosa si tratta: 49

50

CAPITOLO 5. DJANGO

mysite Questa cartella ` il container dellapplicazione principale del sito. e init .py Un le vuoto che indica che la cartella corrente deve essere considerata un package Python. settings.py File di congurazione del progetto. urls.py File contenente le route. ... manage.py ` Questo le ` unutility da riga di comando che permette di gestire varie parti del progetto. E e del tutto analogo ad Artisan di Laravel o rails di RubyOnRails. A dierenza dei framework gi` visti, Django mantiene una struttura piuttosto minimale, evitando a di dover utilizzare directory se non sono necessarie. Django ha una visione particolare rispetto agli altri framework. Un progetto consiste in uninsieme di applicazioni, le quali hanno al loro interno la propria logica (modelli e controller ), e vengono attivate a seconda della richiesta fatta dal browser. Ad esempio, in uno stesso progetto potremmo avere due applicazioni, un blog e un forum, che condividono gli stessi utenti. Vediamo come creare una applicazione allinterno di un progetto: python manage.py startapp blog La struttura generata della nuova applicazione situata in mysite/blog ` la seguente: e init .py Come prima, questo ` un le vuoto che indica che la cartella corrente ` un package Python. e e models.py Questo le contiene i modelli. tests.py Questo le contiene i test che possono essere eseguiti automaticamente. views.py ` Questo le contiene la logica dellapplicazione. E a tutti gli eetti il controller.

5.2

Routing

La gestione delle route si trova allinterno del le mysite/urls.py. Questo le viene anche chiamato URLconf, congurazione degli URL. Vediamo un esempio banale per capirne il funzionamento: mysite/urls.py from django.conf.urls import patterns, include urlpatterns = patterns(, (r^articles/$, include(news.views.all)), ) La prima riga importa i moduli di Django che utilizzeremo. Per denire delle route, assegnamo ad una variabile urlpatterns una lista di tuple del tipo (regular expression, callback function). Nellesempio associamo la view news.views.all agli URL del tipo articles/. Inseriamo una route pi` complessa: u

1 2 3 4 5

5.2. ROUTING urlpatterns += patterns(, (r^articles/(\d{4})/(\d{2})/$, include(news.views.month_archive)), )

51

Lespressione regolare appena creata cattura gli URL del tipo articles/AAAA/MM/GG/ dove AAAA/MM/GG ` la data dellarticolo in formato americano. e Una richiesta del tipo GET /articles/2013/03/08/ verrebbe catturata e Django invocherebbe la funzione news.views.month_archive(2013, 03, 08) Lesempio appena visto utilizza espressioni regolari che non danno nomi alle variabili catturate, e passa gli argomenti alla funzione in ordine. Questo risulta sconveniente nel caso in cui volessimo accettare anche richieste che utilizzano la data in formato europeo, ovvero nella forma GG/MM/AAAA. In Django ` per` possibile anche dare dei nomi alle variabili catturate dalle espressioni regolari; e o vediamo come risultano allora le due route che visualizzano gli articoli usciti in una certa data, espressa in formato europeo o americano: mysite/urls.py from django.conf.urls import patterns, include urlpatterns = patterns(, (r^articles/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$, \ include(news.views.month_archive)), (r^articles/(?P<day>\d{2})/(?P<month>\d{2})/(?P<year>\d{4})/$, \ include(news.views.month_archive)), ) Una richiesta GET /articles/2013/02/08/ chiamerebbe la funzione news.views.month_archive(request, year=2013, month=02, day=08) Una richiesta GET /articles/08/02/2013/ chiamerebbe la funzione news.views.month_archive(request, day=08, month=02, year=2013) La posizione dei parametri ` diversa, ma basta referenziarsi ai nomi dei parametri allinterno e della funzione e la stessa implementazione riesce a servire entrambe le richieste. In Django le route dipendono solo dallURL richiesto, non dal metodo HTTP utilizzato. Sar` a poi compito del controller scegliere lazione da intraprendere. Quindi richieste del tipo GET /users/ e POST /users/ verrebbero catturate dalla stessa route, e delegate alla stessa funzione. Gestione degli errori Quando Django non riesce a trovare una espressione regolare che soddis lURL richiesto, o quando viene lanciata uneccezione, viene invocata una view di gestione dellerrore. Le view da utilizzare in questi casi possono essere specicate utilizzando tre variabili nello URLconf. Ognuna di queste deve essere una funzione o una stringa contenete la view da renderizzare. handler403: Questa variabile indica lazione da intraprendere nel caso di errore 403, HTTP Forbidden, ovvero quando lutente richiede una pagina di cui non ha il permesso di lettura. Di default ` la view e django.views.defaults.permission_denied handler404: Errore 404, ovvero quando la risorsa richiesta non esiste. Di default ` assegnata e alla view django.views.defaults.page_not_found

52

CAPITOLO 5. DJANGO handler500: Errore 500, Internal Server Error, ovvero errore generico del server. Di default verr` richiamata la view a django.views.defaults.server_error

Pressi Possiamo specicare dei pressi comuni per le view nella chiamata alla funzione patterns(), per evitare ridondanza nel codice. Prendiamo questo URLconf: urlpatterns = patterns(, (r^articles/$, news.views.all), (r^articles/(\d{4})/$, news.views.year_archive), (r^articles/(\d{4})/(\d{2})/$, news.views.month_archive), (r^articles/(\d{4})/(\d{2})/(\d+)/$, news.views.article_detail), ) Le view hanno un presso comune news.views. Invece di riscriverlo in ogni riga di urlpattern, possiamo passarlo come primo parametro a patterns(), ottenendo questo risultato: urlpatterns = patterns(news.views, (r^articles/$, all), (r^articles/(\d{4})/$, year_archive), (r^articles/(\d{4})/(\d{2})/$, month_archive), (r^articles/(\d{4})/(\d{2})/(\d+)/$, article_detail), ) Possiamo anche avere due o pi` pressi comuni: in questo caso chiameremo patterns() tante u volte quanti sono i pressi, ma concatenando i risultati: urlpatterns = patterns(news.views, (r^articles/$, all), (r^articles/(\d{4})/$, year_archive), (r^articles/(\d{4})/(\d{2})/$, month_archive), (r^articles/(\d{4})/(\d{2})/(\d+)/$, article_detail), ) urlpatterns += patterns(users.views, (r^users/$, all), (r^users/(\d{3})/$, detail), ) Parametri ssati Ci sono casi in cui ` utile passare parametri aggiuntivi alle view, ma che e non provengo dallURL; pensiamo, ad esempio, di avere ununica funzione di visualizzazione degli articoli. Vogliamo che essa visualizzi tutti gli articoli in archivio nel caso in cui lURL richiesto sia del tipo /archive/, e solo gli ultimi 10 articoli nel caso lURL sia del tipo /latest/. urlpatterns = patterns(news.views, (r^articles/$, index, {limit: 0}), (r^latest/$, index, {limit: 10}), ) In questo modo, di fronte ad una richiesta di tipo GET /latest/ verr` invocata a news.views.list(requst, limit=10)

5.3. VIEW LAYER (CONTROLLER)

53

5.3

View layer (Controller)

Quando una richiesta viene ltrata dal modulo di routing, il controllo passa alla view associata: in Django infatti il concetto di controller , ovvero la parte che ottiene i dati e li prepara per essere visualizzati, viene implementato dalle view . In un progetto Django le view sono sono speciche per ogni applicazione, e sono situate nel le appname/views.py. Vediamo una view banale: news/views.py from django.http import HttpResponse def index(request): return HttpResponse("List of the articles") Questa view banale restituir` una pagina con la stringa List of the articles. Il costruttore a delloggetto HttpResponse accetta una stringa, che ` poi la pagina presentata al client. e Possiamo anche visualizzare i parametri passati: def index(request, limit=0): return HttpResponse("List of the articles. Limit is %s." % limit) Associando questa view allultima route vista, possiamo fare una richiesta GET /latest/ e il risultato sarebbe List of the articles. Limit is 10. Vediamo adesso di implementare seriamente la funzionalit` richiesta, ovvero di visualizzare gli a ultimi n articoli. news/views.py from news.models import Article from django.http import HttpResponse def index(request, limit=0): latest_articles_list = Article.objects.all().order_by(-pub_date) if limit != 0: latest_articles_list = latest_articles_list[:int(limit)] output = , .join([a.title for a in latest_articles_list]) return HttpResponse(output) Lesempio utilizza il modello Article denito nellapplicazione news; questo modello verr` analizzato a meglio nella prossima sezione. Per ora ci basta sapere che la chiamata Article.objects.all().order_by(-pub_date)[:int(limit)] ottiene la lista di tutti gli articoli, ordinati per data di pubblicazione in modo decrescente. Abbiamo poi if limit != 0: latest_articles_list = latest_articles_list[:int(limit)] che inserisce un limite al numero di entry da ottenere dal database. La riga successiva output = , .join([a.title for a in latest_articles_list]) crea la stringa di output, separando con una virgola i titoli degli articoli selezionati.

54

CAPITOLO 5. DJANGO

5.4

Template

Ovviamente questa pratica di codicare il design della pagina direttamente nel codice ` assolutamente e da evitare. Creiamo allora il template della nostra pagina; Django mette a disposizione un template system simile a quello utilizzato da Laravel. Django non da indicazioni su quale sia la cartella in cui inserire i template, ma ` comunque buona norma mantenerli allinterno del progetto, ad esempio e in una cartella chiamata templates/. In questo caso ` necessario denire il path in cui Django andr` poi a cercare i template che e a richiamiamo. Questo dovr` essere un path assoluto: a mysite/settings.py ... TEMPLATE_DIRS = ( /abspath/to/project/mysite/templates), ) ... Riprendiamo lultima view vista, ma stavolta creiamo un template che visualizza gli articoli in modo strutturato: mysite/templates/news/index.html <html> <head> <title>My awesome blog!</title> </head> <body> <h1>Articles index</h1> {% for article in articles %} <h2>{{ articles.title }} </h2> <p>{{ article.content }} </p> <hr /> {% endfor %} </body> </html> Il template appena creato si aspetta di poter utilizzare la variabile articles per poter visualizzare gli articoli contenuti, e quindi nella view richiameremo il rendering del template in questo modo: ... def index(request, limit=0): ... return render_to_response(news/index.html, {articles: latest_articles_list}) render to response vuole come primo parametro obbligatorio il nome del template da renderizzare, e come parametro opzionale un array associativo contenente i dati visibili nella view, come gi` visto in CodeIgniter o Laravel. a Estensione di template Come gi` visto in Laravel e Rails, ` possibile creare template di base a e che verranno poi estesi da altre pagine. Possiamo infatti denire la struttura base per il nostro sito in questo modo: mysite/template/base.html <!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>{% block title %} My amazing site{% endblock %} </title> </head>

5.4. TEMPLATE <body> <nav> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/news/">Blog</a></li> </ul> {% endblock %} </nav> <div class="content"> {% block content %}{% endblock %} </div> </body> </html>

55

Questo ` semplicemente lo scheletro comune a tutte le pagine del sito, in cui sono presenti tre e blocchi, il titolo, la barra di navigazione e il contenuto. Notiamo che per il titolo e per la barra di navigazione ` presente del testo tra {% block block name %} e {% endblock %}, mentre il e contenuto ` vuoto. Questo ` dovuto al fatto che il contenuto dovr` essere riempito dalle pagine e e a glie, mentre il titolo e la barra di navigazione possono rimanere le stesse. La pagina per la visualizzazione di un articolo si trasformerebbe in questo modo: mysite/templates/news/article detail.html {% extends "base.html" %} {% block title %}{{ a.title }} - {{ block.super }}{% endblock %} {% block content %} <h1>{{ a.title }} </h1> <h2>Written by: {{ a.author }} </h2> <p>{{ article.content }} </p> {% endblock %} Il tag extends ` quello chiave: informa Django di voler estendere il template base.html. I successivi e tag block rideniscono i blocchi corrispondenti presenti nel template padre. Da notare che non abbiamo modicato il blocco associato alla barra di navigazione; questo prender` il valore denito dal padre. Il codice HTML generato sar` il seguente: a a <!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>Web Application Frameworks - My amazing site</title> </head> <body> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/news/">Blog</a></li> </ul> </nav> <div class="content"> <h1>Web Application Frameworks</h1> <h2>Written by: Alessio Bogon</h2>

56

CAPITOLO 5. DJANGO

<p> Mettiamoci nei panni di un programmatore a cui viene commissionata una semplice applicazione web: un semplice ... </p> </div> </body> </html>

5.5

Models

Non poteva mancare in Djano il mapping tra oggetti e tabelle del database, ovvero lObjectrelational Mapping. Analogamente a Rails o Laravel, ogni modello denito si riferisce ad una tabella e contiene metodi per accedere e modicare gli attributi delle istanze. I modelli vanno deniti nel le models.py, allinterno della nostra applicazione. Deniamo, ad esempio, un modello Person che contiene nome e cognome di una persona: from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) Allinterno della classe Person deniamo due variabili, first name e last name, indicando che devono essere dei CharField di lunghezza massima di 30 caratteri. Notiamo subito una dierenza sostanziale tra i framework visti no ad ora: mentre in Rails, Laravel, o la maggior parte dei framework lapproccio per la gestione dei dati ` database-centric, e ovvero guidato dal database, in Django ` application-centric, ovvero gestito dallapplicazione. e In Laravel, ad esempio, bastava denire il nome della classe per poter accedere alla tabella associata, in quanto le informazioni venivano estrapolate dal database al momento dellutilizzo. In Django invece dobbiamo denire la struttura degli oggetti nellapplicazione. Questo si traduce nel fatto che il database diventa un semplice meccanismo di gestione della persistenza dei dati. Gli attributi, i tipi, e le relazioni vengono deniti nei modelli di Django e successivamente attraverso un comando potremo creare le tabelle nel database che rispecchiano i modelli deniti. Questo meccanismo elimina anche il concetto di migrazione: infatti la struttura del database rispecchier` sempre la struttura dellapplicazione in un determinato istante. a Per sincronizzare la struttura del database con il modello appena creato ` suciente lanciare il e comando python manage.py syncdb Utilizzando come modello lesempio appena visto, verr` eseguita la seguente query: a CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL ); Django fa alcune scelte per noi: Il nome della tabella di default ` della forma nomeapp nomeclasse, quindi dato che lape ` plicazione ` myapp allora la tabella si chiama myapp person. E possibile anche congurare e manualmente il nome della tabella. ` Viene aggiunto automaticamente un campo id come chiave primaria. E ovviamente possibile modicare questo comportamento. La sintassi utilizzata per creare la query varia a seconda del DBMS in uso. Quella vista nellesempio ` per PostgreSQL. e

5.5. MODELS

57

Una volta denito un modello, dobbiamo registrarlo per poterlo utilizzare. Per farlo bisogna aggiungere il nome dellapplicazione allarray INSTALLED APPS nel le di congurazione di Django: mysite/settings.py ... INSTALLED_APPS = ( myapp, ) ... Attributi La parte pi` importante di un modello sono i suoi attributi, che vengono poi esportati u nel database. Django mette a disposizione una dozzina di tipi diversi, i quali rispecchiano pi` o u meno quelli presenti nella maggior parte dei DBMS. Per dichiarare un attributo istanziamo una delle sottoclassi di django.db.models.Field, come ad esempio CharField. Alcune di queste classi hanno parametri obbligatori come la lunghezza massima del campo. Esistono anche parametri opzionali comuni a tutte queste classi: null Se settato a True, il valore nullo ` accettato. e blank Se True, il valore pu` essere lasciato vuoto. o choices Denisce il dominio per quel campo: questo valore deve essere una lista di coppie chiavevalore, in cui la chiave ` quello che sar` salvato sul database, e il valore ` quello che verr` e a e a visualizzato nellinterfaccia utente. default Il valore di default per il campo. prumary key Se True, questo campo sar` la chiave primaria per la tabella. Se non viene specicata a alcuna primary key nel modello, ne verr` creata automaticamente una di nome id e tipo a IntegerField. unique Se True il campo deve essere univoco. Vediamo un esempio pi` complesso che utilizza alcuni di questi argomenti: u class User(models.Model): email = models.EmailField(max_length=75, primary_key=True) username = models.CharField(max_length=30) password = models.CharField(max_length=100) first_name = models.CharField(max_length=50) middle_name = models.CharField(max_length=50, null=True) last_name = models.CharField(max_length=50) sex = models.CharField(max_length=2, choices=( (uF, uFemale), (uM, uMale), )) about_me = models.TextField(blank=True) Relazioni Possiamo denire anche relazioni tra i modelli:

class Manufacturer(models.Model): # ... class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer) # ...

58

CAPITOLO 5. DJANGO

In questo esempio il campo manufacturer di Car viene dichiarato come chiave esterna, e alla funzione viene passato la classe referenziata. Per le relazioni di tipo molti a molti si utilizza la classe models.ManyToManyField, che vuole come parametro del costruttore il modello referenziato. Per poter rappresentare questa relazione nel RDBMS, Django crea una tabella intermedia. Vediamo inne come si possono utilizzare i modelli. Deniamo Publication e Article: from django.db import models class Publication(models.Model): title = models.CharField(max_length=30) def __unicode__(self): return self.title class Meta: ordering = (title,) # ordinamento per titolo class Article(models.Model): headline = models.CharField(max_length=100) publications = models.ManyToManyField(Publication) def __unicode__(self): return self.headline class Meta: ordering = (headline,) # ordinamento per headline Le query generate saranno le seguenti (sintassi SQLite): CREATE TABLE "news_publication" ( "id" integer NOT NULL PRIMARY KEY, "title" varchar(30) NOT NULL ); CREATE TABLE "news_article_publications" ( "id" integer NOT NULL PRIMARY KEY, "article_id" integer NOT NULL, "publication_id" integer NOT NULL REFERENCES "news_publication" ("id"), UNIQUE ("article_id", "publication_id") ); CREATE TABLE "news_article" ( "id" integer NOT NULL PRIMARY KEY, "headline" varchar(100) NOT NULL ); Possiamo ora manipolare i dati nel modo seguente: # Creo 3 pubblicazioni p1 = Publication(title=The Python Journal) p1.save() p2 = Publication(title=Science News) p2.save() p3 = Publication(title=Science Weekly) p3.save() # Creo un articolo a1 = Article(headline=Django lets you build Web apps easily) a1.save()

5.5. MODELS # Associo larticolo alla prima pubblicazione a1.publications.add(p1) # Creo un altro articolo che sara presente in tutte le pubblicazioni a2 = Article(headline=NASA uses Python) a2.save() a2.publications.add(p1) # Posso aggiungere piu pubblicazioni contemporaneamente a2.publications.add(p2, p3) # Creo una pubblicazione e aggiungo larticolo a2 in un passo unico new_pub = a2.publications.create(title=Python for dummies) # Accedo alle pubblicazioni Publication.objects.all() # => [<Publication: Python for dummies>, <Publication: Science News>, # <Publication: Science Weekly>, <Publication: The Python Journal>] # E possibile accedere alle pubblicazioni di un certo articolo a1.publications.all() # => [<Publication: The Python Journal>] # Possiamo accedere anche agli articoli di una pubblicazione p1.article_set.all() # => [<Article: Django lets you build Web apps easily>, # <Article: NASA uses Python>] # Possiamo rimuovere un articolo da una pubblicazione, senza rimuoverlo dal # database p1.article_set.remove(a1) # Ora non e piu presente tra gli articoli di p1 p1.article_set.all() # =>[<Article: NASA uses Python>] # e p1 non e piu presente tra le pubblicazioni di a1 a1.publications.all() # => [] # Cancelliamo a1 a1.delete() Possiamo anche ltrare i dati in questo modo: # Seleziona la pubblicazione con id=1 Publication.objects.filter(id=1) # => [<Publication: The Python Journal>] # Solitamente si usa specificare la primary key con pk # in questo caso Django controlla lattributo definito come primary key Publication.objects.filter(pk=1) # => [<Publication: The Python Journal>] # Seleziona le pubblicazioni che hanno il titolo che inizia con Science Publication.objects.filter(title__startswith=Science) # => [<Publication: Science News>, <Publication: Science Weekly>]

59

60

CAPITOLO 5. DJANGO

# Seleziona gli articoli che non sono stati pubblicati su riviste # che contengono la parola Science nel titolo Article.objects.exclude(publications__title__contains=Science) Django ci permette di ltrare i dati semplicemente inserendo loperatore nel nome del parametro formale della funzione filter. Specicando ad esempio filter(title startswith=Science) indichiamo che vogliamo ottenere le entry aventi il titolo che comincia con Science. La sintassi quindi ` del tipo attributo operatore=operando. Il discorso ` analogo per la funzione exclude, e e la quale esclude dal risultato tutte le entry che soddisfano la condizione indicata. Nellultima query vediamo che ` possibile anche ltrare utilizzando le relazioni, estendendo la e sintassi a foreignkey attributo operatore=operando.

5.6

Interfaccia di amministrazione

Generare le pagine di amministrazione per permettere agli utenti di gestire i contenuti ` un lavoro e tedioso che non richiede molta creativit` da parte del programmatore. Per questa ragione, Django a automatizza interamente la creazione dellinterfaccia di amministrazione dei modelli. Django ` stato pensato per gestire siti di notizie, e per questo esiste una chiara distinzione e tra linterfaccia per la gestione dei contenuti e linterfaccia pubblica a cui gli utenti accedono. Solitamente per questo tipo di applicazioni abbiamo due tipi di utenti: gli editori, i quali gestiscono i contenuti, e gli utenti standard, ovvero quelli che possono solo leggerli. Soltanto i primi avranno i permessi per accedere allarea amministrativa. In Django ` necessario prima abilitarla, in quanto ` possibile che non ce ne sia bisogno. Per fare e e ci` ` necessario eseguire le seguenti azioni: oe 1. Aggiungere la stringa che rappresenta lapplicazione di amministrazione nel le di congurazione: mysite/settings.py ... INSTALLED_APPS = ( ... django.contrib.admin, ) 2. Eseguire il comando python manage.py syncdb Questo ` necessario perch ` stata aggiunta una nuova applicazione, e quindi bisogna aggiornare e ee la struttura del database. 3. Modicare il le URLconf come segue: ... # Ricerca delle applicazioni abilitate allamministrazione from django.contrib import admin admin.autodiscover() ... urlpatterns += patterns(, # Link alle route registrate dallinterfaccia admin url(r^admin/, include(admin.site.urls)), ) 4. Abilitare i modelli delle singole applicazioni allamministrazione. Se ad esempio avessimo unapplicazione di sondaggi, polls, allora doveremmo creare il le polls/admin.py:

5.6. INTERFACCIA DI AMMINISTRAZIONE polls/admin.py from polls.models import Poll from django.contrib import admin admin.site.register(Poll) ...

61

Chiamando admin.site.register(Poll) abbiamo abilitato il modello Poll dellapplicazione polls. Supponiamo di avere i seguenti modelli nella nostra nuova applicazione: polls/models.py from django.db import models class Poll(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField(date published) class Choice(models.Model): poll = models.ForeignKey(Poll) choice = models.CharField(max_length=200) votes = models.IntegerField()

Eseguiamo quanto visto prima per abilitare lamministrazione, e avviamo il server integrato per vedere come si presenta: python manage.py runserver Ora accediamo con un browser a http://localhost:8000/admin e troveremo la schermata di login:

Una volta entrati con le credenziali scelte precedentemente, avremo la possibilit` di gestire a gruppi, utenti e le applicazioni registrate:

62

CAPITOLO 5. DJANGO

Cliccando su Polls possiamo amministrare i sondaggi:

Cliccando il sondaggio possiamo modicarlo:

Django ci permette di modicare molte opzioni dellarea amministrativa, per venire incontro alle varie esigenze. Per fare questo ` necessario creare delle classi di tipo nomemodelloAdmin e allinterno del le admin.py. Vediamo come modicare la visualizzazione del sondaggio, lultima che abbiamo visto: polls/admin.py from polls.models import Poll from django.contrib import admin class PollAdmin(admin.ModelAdmin): fieldsets = [ (None, {fields: [question]}), (Date information, {fields: [pub_date], classes: [collapse]}), ] admin.site.register(Poll, PollAdmin)

5.6. INTERFACCIA DI AMMINISTRAZIONE

63

In questo modo impostiamo quali campi visualizzare e come visualizzarli: ` possibile denire la e variabile fieldset per creare gruppi di campi. Nel primo gruppo, senza nome, vogliamo che venga visualizzato solo il campo question. Il secondo gruppo invece vogliamo chiamarlo Date information e al suo interno vogliamo il campo pub date. Come parametro passiamo anche un argomento classes con valore collapse: questo fa s` che tutto il gruppo di default venga nascosto:

Modichiamo di nuovo il le per poter permettere di votare i sondaggi:

from polls.models import Poll, Choice from django.contrib import admin class ChoiceInline(admin.TabularInline): model = Choice extra = 3 class PollAdmin(admin.ModelAdmin): fieldsets = [ (None, {fields: [question]}), (Date information, {fields: [pub_date], classes: [collapse]}), ] inlines = [ChoiceInline] admin.site.register(Poll, PollAdmin)

Abbiamo inserito una classe ChoiceInline, che estende admin.TabularInline. Questultima permette di visualizzare oggetti in una tabella compatta. Al suo interno deniamo il modello a cui si riferisce, Choice, e il numero di scelte da visualizzare inizialmente, in questo caso 3. Inserendo poi inlines = [ChoiceInline] allinterno della classe PollAdmin, Django riconosce la relazione tra i due modelli e visualizza le scelte che appartengono a quel sondaggio:

64

CAPITOLO 5. DJANGO

Esistono altre svariate opzioni per modicare ad hoc linterfaccia amministrativa, per questo rimandiamo alla documentazione uciale.

5.7

Conclusioni

Django ` un ottimo framework, che permette di avere funzionante unintera applicazioni in poco e tempo. Infatti abbiamo visto come sia facile generare unintera parte di amministrazione a partire da poche classi. Questo lo rende perfetto, ad esempio, nel caso in cui avessimo delle deadline strette da rispettare. Inoltre il codice che andremo a scrivere ` molto pulito, grazie al fatto che Django e mette a disposizione classi molto ricche ma anche perch Python stesso ` stato scritto in modo tale e e da avere unottima leggibilit` del codice. a Gli aspetti negativi pi` rilevanti, a mio avviso, troviamo la scarsa adabilit` della documenu a tazione. Infatti esistono classi, come TabularInline, che non sono neanche menzionate nella documentazione, se non nei tutorial. Unaltra mancanza ` la possibilit` di creare controller RESTe a ful, se non attraverso dei plugin. In realt` in Django abbiamo gi` visto che non esiste il concetto di a a controller inteso come nel pattern MVC, ma esistono soltanto le view che determinano i dati da visualizzare. Questo si ripercuote anche sulla gestione delle url: per ogni view infatti dobbiamo denire le route che vi accedono, e questo pu` rivelarsi pesante nel caso di applicazioni che hanno o bisogno, ad esempio, della gestione CRUD delle risorse.