Development

Documentation/it_IT/book/1.1/08-Inside-the-Model-Layer (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/it_IT/book/1.1/08-Inside-the-Model-Layer

Show
Ignore:
Author:
garak (IP: 83.103.98.10)
Timestamp:
10/16/08 14:40:12 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/it_IT/book/1.1/08-Inside-the-Model-Layer

    v0 v1  
     1{{{ 
     2#!WikiMarkdown  
     3 
     4Capitolo 8 - All'interno del layer Modello 
     5========================================== 
     6 
     7Fino ad ora gran parte della discussione è stata dedicata alla costruzione delle pagine ed all'elaborazione delle richieste e delle risposte. Ma la business logic di un'applicazione web si basa per lo più sul modello dei dati. Il modello in symfony si basa su una mappatura oggetto/relazione conosciuta come progetto Propel ([http://propel.phpdb.org](http://propel.phpdb.org)). In un'applicazione symfony, accedi ai dati memorizzati in un database tramite oggetti; non ti riferisci mai al database direttamente. Questo permette un alto livello di astrazione e portabilità. 
     8 
     9Questo capitolo spiega come creare un modello dei dati, ed il modo di accedere e modificare dati in Propel. Dimostra inoltre l'integrazione di Propel in symfony. 
     10 
     11 
     12Perché usare un ORM ed un livello di astrazione? 
     13------------------------------------------------ 
     14 
     15I database sono relazionali. Symfony e PHP 5 sono orientati agli oggetti. Per poter accedere efficacemente al database in un contesto orientato agli oggetti, occorre disporre di un'interfaccia che traduca l'oggetto logico in relazionale. Come spiegato nel Capitolo 1, tale interfaccia è chiamata object-relational mapping (ORM), ed è composta di oggetti che forniscono accesso ai dati e contengono le business rules al proprio interno. 
     16 
     17Il più grande vantaggio di ORM è la riusabilità, che permette ai metodi dei data object di essere chiamati da varie parti dell'applicazione, o anche da diverse applicazioni. Inoltre il layer ORM incapsula la data logic; ad esempio, il calcolo del rating di un utente di un forum basato sul numero dei suoi contributi dalla loro popolarità. Quando una pagina deve mostrare tale rating, chiama semplicemente un metodo del modello, senza preoccuparsi dei dettagli del calcolo. Se l'algoritmo di tale calcolo dovesse cambiare in futuro, dovresti solo modificarlo nel modello, lasciando inalterato il resto dell'applicazione. 
     18 
     19Utilizzare oggetti invece di record e classi invece di tabelle ha un altro beneficio: ti permette di aggiungere componenti ai tuoi oggetti senza che questi siano necessariamente colonne in una tabella. Ad esempio, se tu avessi una tabella `client` con due campi chiamati `first_name` e `last_name`, potresti avere la capacità di richiedere semplicemente `Name`. In un mondo orientato agli oggetti, è semplice quanto aggiungere un metodo alla classe `Client`, come mostrato nel Listato 8-1. Dal punto di vista dell'applicazione, non c'è differenza tra gli attributi `Name`, `FirstName` o `LastName` della classe `Client`. Solo la classe stessa può determinare quale corrispondenza esiste tra colonna e attributo. 
     20 
     21Listato 8-1 - Gli accessori nascondono l'effettiva struttura di una tabella 
     22 
     23    [php] 
     24    public function getName() 
     25    { 
     26      return $this->getFirstName.' '.$this->getLastName(); 
     27    } 
     28 
     29Tutte le ripetitive funzioni di accesso ai dati e la business logic dei dati stessi possono venire memorizzate in oggetti simili. Supponi di avere un oggetto `ShoppingCart` nel quale puoi inserire `Items` (che sono oggetti). Per sapere il costo totale dell'acquisto, puoi scrivere un metodo per il calcolo, come mostrato nel Listato 8-2. 
     30 
     31Listato 8-2 - Gli accessori nascondono la data logic 
     32 
     33    [php] 
     34    public function getTotal() 
     35    { 
     36      $total = 0; 
     37      foreach ($this->getItems() as $item) 
     38      { 
     39        $total += $item->getPrice() * $item->getQuantity(); 
     40      } 
     41      
     42      return $total; 
     43    } 
     44 
     45C'è un altro punto importante da considerare quando si costruiscono procedure di accesso ai dati: i produttori di database usano differenti varianti della sintassi SQL. Cambiare database management system (DBMS) significherebbe dover riscrivere parte delle query che erano state scritte per quello precedente. Se invece costruisci le tue query con una sintassi indipendente dal database, e lasci l'esecuzione effettiva della transazione SQL ad un altro componente, puoi cambiare database in modo indolore. Questa è la finalità del layer di astrazione del database. Esso ti forza ad usare una sintassi specifica per le query, e fa tutto il lavoro sporco di collegamento al db ed ottimizzazione del codice SQL. 
     46 
     47Il vantaggio principale di tale layer di astrazione è quindi la portabilità, perché ti permette di cambiare database anche nel mezzo di un progetto. Immagina di dover scrivere rapidamente un prototipo di un'applicazione quando il cliente ancora non ha deciso quale sia il database più adatto allo scopo. Puoi cominciare a scrivere l'applicazione son SQLite, ad esempio, e cambiare in seguito con MySQL, PostgreSQL, od Oracle quando il cliente ha deciso cosa utilizzare. C'è semplicemente da cambiare una linea in un file di configurazione, e tutto continua a funzionare perfettamente. 
     48 
     49Symfony usa Propel come ORM, e Propel usa Creole come astrazione del database. Queste due componenti di terze parti, entrambe sviluppate dal team di Propel, sono integrate trasparentemente in Symfony, e le puoi considerare come fossero parte del framework. Le loro sintassi e convenzioni, descritte in questo capitolo, sono state adattate per essere il meno dissimile possibile da quelle di Symfony. 
     50 
     51>**NOTE** 
     52>In un progetto di symfony tutte le applicazioni condividono lo stesso modello. Questo è il punto del livello progetto: raggruppare applicazioni che si basano sulle stesse business rules. Questa è la ragione per cui il modello è indipendente dalle applicazioni ed i file del modello sono memorizzati nella cartella <span style="font-family: Monospace">lib/model/</span> nella root del progetto. 
     53 
     54 
     55Schema del database di Symfony 
     56------------------------------ 
     57 
     58Per poter creare il modello dei dati che verrà usato da Symfony, occorre tradurre qualsiasi modello relazionale possieda il tuo db in un object data model. ORM necessita di una descrizione del modello relazionale per poter fare la mappatura, che viene chiamata schema. In tale schema, si descrivono le tabelle, le relazioni e le caratteristiche delle loro colonne. 
     59 
     60La sintassi di symfony per gli schemi utilizza il formato YAML. Il file `schema.yml` deve essere posizionato nella cartella `myproject/config/`. 
     61 
     62>**NOTE** 
     63>Symfony è in grado di utilizzare anche il formato XML, come spiegato più avanti in questo capitolo nella sezione "Dietro schema.yml: schema.xml". 
     64 
     65 
     66### Esempi di schema 
     67 
     68Come traduci la struttura di un database in uno schema? Un esempio è il metodo migliore per capirlo. Immagina di avere il database di un blog con due tabelle: `blog_article` e `blog_comment`, con la struttura mostrato nella Figura 8-1. 
     69 
     70Figura 8-1 - Struttura del database di un blog 
     71 
     72<img src="http://www.symfony-project.com/images/book/trunk/F0801.png" /> 
     73 
     74Il relativi file `schema.yml` dovrebbe essere come quello del Listato 8-3. 
     75 
     76Listato 8-3 - Esempio di schema.yml 
     77 
     78    [php] 
     79    propel: 
     80      blog_article: 
     81        _attributes: { phpName: Article } 
     82        id: 
     83        title:       varchar(255) 
     84        content:     longvarchar 
     85        created_at: 
     86      blog_comment: 
     87        _attributes: { phpName: Comment } 
     88        id: 
     89        article_id: 
     90        author:      varchar(255) 
     91        content:     longvarchar 
     92        created_at: 
     93 
     94 
     95Da notare che il nome del database stesso (`blog`) non appare nel file. Invece, il database è descritto tramite il nome di una connessione (`propel` in questo esempio). Questo perché le effettive impostazioni di connessione dipendono dall'ambiente nel quale sta girando. Ad esempio, quando la tua applicazione gira nell'ambiente di sviluppo si collegherà ad un database di sviluppo (come `blog_dev`), ma con lo stesso schema del database di produzione. Le impostazioni di connessione saranno specificate nel file `databases.yml`, descritte nella sezione "Connessioni al database" più avanti in questo capitolo. Lo schema non contiene alcun riferimento alla connessione, tranne il nome, per mantenere l'astrazione del database. 
     96 
     97 
     98### Sintassi di base dello schema 
     99 
     100In un file `schema.yml`, la prima chiave rappresenta il nome della connessione. Può contenere diverse tabelle, ognuna avente un insieme di colonne. Secondo la sintassi YAML, la chiave finisce con i due punti, e la struttura viene mostrata tramite indentazione (uno o più spazi vuoti, non tabulazioni). 
     101 
     102Una chiave può possedere attributi speciali, come `phpName` (nome della classe che verrà generata). Se non specifichi un `phpName` per una tabella, symfony ne creerà una basata sulla versione camelCase della tabella. 
     103 
     104>**TIP** 
     105>La convenzione camelCase elimina gli underscore dalle parole e rende maiuscola la prima lettera delle parole interne. Le versioni camelCase di default di `blog_article` e `blog_comment` sono `blogArticle` e `blogComment`. Il nome di questa convenzione viene dall'alternarsi di lettere maiuscole all'interno di parole lunghe, che ricordano le gobbe di un cammello. 
     106 
     107Una tabella contiene colonne. Il valore di una colonna può essere definito in tre modi diversi: 
     108 
     109  * Se non specifichi niente, symfony cercherà di assegnare gli attributi secondo una serie di convenzioni che verranno spiegate più avanti in questo capitolo sotto la sezione "Colonne vuote". Ad esempio, la colonna `id` del Listato 8-3 non ha bisogno di essere definita. Symfony assegnerà integer auto-increment, chiave primaria della tabella. Nella tabella `blog_comment`, `article_id` verrà riconosciuta come chiave importata della tabella `blog_article` (le colonne che finiscono con `_id` sono considerate come chiavi importate, e la tabella relativa viene specificata dalla parte prima del nome della colonna). Colonne che si chiamano `created_at` (o `updated_at`) vengono impostate automaticamente come di tipo `timestamp`. Per tutte queste colonne, non hai bisogno di specificare il tipo. Questa è una delle ragioni per cui `schema.yml` è facile da scrivere. 
     110  * Se specifichi un solo attributo, è il tipo di colonna. Symfony capisce i tipi standard: `boolean, integer, float, date, varchar(size), longvarchar` (convertito, ad esempio, in `text` in MySQL). Nota che i tipi `date` e `timestamp` soffrono delle solite limitazioni delle date di Unix, e non possono essere impostate a date precedenti al 01/01/1970. Se avessi bisogno di utilizzare date più vecchie, sono disponibili i tipi "before Unix" `bu_date` e `bu_timestamp`. 
     111  * Se avessi bisogno di definire altri attributi per le colonne (come valore di default, required e così via), dovresti scriverli come coppie `key: value`. Tale sintassi estesa dello schema è spiegata più avanti in questo capitolo. 
     112 
     113Le colonne possono anche avere l'attributo `phpName`, che è la versione in maiuscolo del nome (`Id, Title, Content`, ecc...) e nella maggior parte dei casi non ha bisogno di override. 
     114 
     115Le tabelle possono anche avere chiavi importate ed indici espliciti, come anche definizioni di strutture specifiche del database. Troverai maggiori dettagli in proposito nella sezione "Sintassi estesa dello schema" più avanti in questo capitolo. 
     116 
     117 
     118Classi del modello 
     119------------------ 
     120 
     121Lo schema viene utilizzato per costruire il modello delle classi del layer ORM. Per risparmiare tempo di esecuzione, tali classi vengono generate a linea di comando con la chiamata `propel:build-model`. 
     122 
     123    > symfony propel:build-model 
     124 
     125Lanciando questo comando verrà effettuata l'analisi dello schema e la generazione delle classi base del modello nella cartella `lib/model/om/` del tuo progetto: 
     126 
     127  * BaseArticle.php 
     128  * BaseArticlePeer.php 
     129  * BaseComment.php 
     130  * BaseCommentPeer.php 
     131 
     132Inoltre, le classi effettive del modello verranno create nella cartella `lib/model/`: 
     133 
     134  * Article.php 
     135  * ArticlePeer.php 
     136  * Comment.php 
     137  * CommentPeer.php 
     138 
     139Hai definito solo due tabelle e ottieni otto file. Non c'è niente di sbagliato, ma merita una spiegazione. 
     140 
     141### Classi di base e personalizzate 
     142 
     143Perché tenere due versioni dello stesso modello in due cartelle differenti? 
     144 
     145Probabilmente avrai la necessità di aggiungere proprietà e metodi personalizzati al modello (ricorda il metodo `getName()` del Listato 8-1). Ma lo sviluppo del progetto porterà sicuramente anche all'aggiunta di tabelle e/o colonne. Ogni volta che cambi il file `schema.yml`, dovrai rigenerare le classi del modello tramite una chiamata propel:build-model. Se i tuoi metodi fossero scritti nelle classi effettivamente generate, andrebbero persi ogni volta. 
     146 
     147Le classi Base nella cartella `lib/model/om/` sono quelle generate direttamente dallo schema. Non dovresti mai modificarle, dato che ogni nuova ri-generazione le riscriverà da zero. 
     148 
     149D'altra parte, le classi personalizzate dentro `lib/model/` erediteranno direttamente dalle Base. Quando viene chiamato il comando `propel:build-model` su un modello esistente, queste classi non vengono toccate. Quindi esse sono il posto giusto dove aggiungere i tuoi metodi. 
     150 
     151Il Listato 8-4 presenta un modello personalizzato di classe creato alla prima chiamata di `propelbuild-model`. 
     152 
     153Listato 8-4 - Esempio di classe di modello, in `lib/model/Article.php` 
     154 
     155 
     156    [php] 
     157    <?php 
     158     
     159    class Article extends BaseArticle 
     160    { 
     161    } 
     162 
     163Esso eredita tutti i metodi della classe `BaseArticle`, ma una modifica allo schema non lo toccherà. 
     164 
     165Questo meccanismo di classi personalizzate che estendono le Base ti permette di cominciare a scrivere del codice anche senza conoscere il modello relazionale finale. La relativa struttura di file rende il modello sia personalizzabile che evolvibile. 
     166 
     167 
     168### Oggetti e classi Peer 
     169 
     170`Article` e `Comment` sono classi di oggetti che rappresentano record nel database. Essi forniscono accesso alle colonne di un record ed ai record relativi. Ciò significa che tu potrai sapere il titolo di un articolo chiamando un metodo dell'oggetto `Article`, come mostrato nel Listato 8-5. 
     171 
     172Listato 8-5 - Sono disponibili Getter per le colonne nella classe oggetto 
     173 
     174    [php] 
     175    $article = new Article(); 
     176    // ... 
     177    $title = $article->getTitle(); 
     178 
     179`ArticlePeer` e `CommentPeer` sono classi peer; ovvero classi che contengono metodi statici per operare sulle tabelle. Esse forniscono un modo di recuperare dati dalle tabelle. I loro metodi di solito restituiscono un oggetto od una collezione di oggetti della classe relativa, come mostrato nel Listato 8-6. 
     180 
     181Listato 8-6 - Metodi statici per recuperare record nella classe oggetto 
     182 
     183    [php] 
     184    // $articles è un array di oggetti della classe Article 
     185    $articles = ArticlePeer::retrieveByPks(array(123, 124, 125)); 
     186 
     187 
     188>**NOTE** 
     189>Dal punto di vista del modello, non ci potrebbe essere alcun oggetto peer. Per tale motivo i metodi delle classi peer vengono acceduti tramite `::` (chiamata statica), invece del solito `->` (istanza del metodo). 
     190 
     191Così, tra le classi oggetto e quelle peer si ottengono quattro classi generate per tabella descritte nello schema. In effetti, esiste un quinto file nella cartella `lib/model/map/`, che contiene metadata sulle tabelle per l'ambiente di runtime. Ma probabilmente non dovrai mai cambiare questo file, per cui non è interessante. 
     192 
     193 
     194Accedere ai dati 
     195---------------- 
     196 
     197In symfony si accede ai tuoi dati tramite oggetti. Se sei abituato al modello relazionale ed all'utilizzo di query SQL per il recupero dei dati, il modello ad oggetti probabilmente ti potrà sembrare complicato. Ma, ancora più probabilmente, dopo aver capito la potenza del modello ad oggetti ti piacerà molto di più. 
     198 
     199Prima di tutto vediamo di utilizzare lo stesso vocabolario. I modelli relazionale e ad oggetti sono concetti simili, ma entrambi possiedono una propria nomenclatura: 
     200 
     201Relazionale    | Object-Oriented 
     202-------------- | --------------- 
     203Tabella        | Classe 
     204Riga, record   | Oggetto 
     205Campo, colonna | Proprietà 
     206 
     207 
     208### Recuperare il valore di una colonna 
     209 
     210Quando symfony costruisce il modello, crea una classe base per ogni tabella definita nello `schema.yml`. Ognuna di queste classi possiede i costruttori, accessori e mutatori di default, basati sulla definizione delle colonne: i metodi `new`, `getXXX()` e `setXXX()` aiutano a creare oggetti ed accedere alle loro proprietà, come mostrato nel Listato 8-7. 
     211 
     212Listato 8-7 - Metodi delle classi generati 
     213 
     214    [php] 
     215    $article = new Article(); 
     216    $article->setTitle('My first article'); 
     217    $article->setContent('This is my very first article.\n Hope you enjoy it!'); 
     218     
     219    $title   = $article->getTitle(); 
     220    $content = $article->getContent(); 
     221 
     222>**NOTE** 
     223>La classe generata si chiama `Article`, che è il `phpName` dato alla tabella `blog_article`. Se il `phpName` non fosse stato definito nello schema, la classe si sarebbe chiamata `BlogArticle`. Gli accessori ed i mutatori utilizzano una variante camelCase del nome delle colonne, quindi il metodo `getTitle()` recupera il valore della colonna `title`. 
     224 
     225Per impostare diversi campi in una volta, puoi usare il metodo `fromArray()`, anch'esso generato per ogni classe, come mostrato nel Listato 8-8. 
     226 
     227Listato 8-8 - Il metodo `fromArray()` è un setter multiplo 
     228 
     229    [php] 
     230    $article->fromArray(array( 
     231      'title'   => 'My first article', 
     232      'content' => 'This is my very first article.\n Hope you enjoy it!', 
     233    )); 
     234 
     235### Recuperare record relativi 
     236 
     237La colonna `article_id` della tabella `blog_comment` definisce implicitamente una chiave importata dalla tabella `blog_article`. Un commento è relativo ad un articolo, ed ogni articolo può avere diversi commenti. Le classi generate contengono cinque metodi che traducono questo tipo di relazione in un modo orientato agli oggetti: 
     238 
     239  * `$comment->getArticle()`: per recuperare l'oggetto `Article` relativo 
     240  * `$comment->getArticleId()`: per recuperare l'ID dell'oggetto `Article` relativo 
     241  * `$comment->setArticle($article)`: per impostare l'oggetto `Article` relativo 
     242  * `$comment->setArticleId($id)`: per impostare l'oggetto `Article` relativo da un ID 
     243  * `$article->getComments()`: per recuperare i relativi oggetti `Comment` 
     244 
     245I metodi `getArticleId()` e `setArticleId()` mostrano che tu puoi considerare la colonna article_id come una colonna normale e impostare le relazioni manualmente, ma ciò non è molto interessante. I benefici del mondo object-oriented sono molto più evidenti negli altri tre metodi. Il Listato 8-9 mostra l'utilizzo dei setter generati. 
     246 
     247Listato 8-9 - Chiavi importate trasformate in setter speciali 
     248 
     249    [php] 
     250    $comment = new Comment(); 
     251    $comment->setAuthor('Steve'); 
     252    $comment->setContent('Gee, dude, you rock: best article ever!); 
     253     
     254    // Collega questo commento al precedente oggetto $article 
     255    $comment->setArticle($article); 
     256     
     257    // Sintassi alternativa 
     258    // Ha senso solo se l'oggetto è già memorizzato nel db 
     259    $comment->setArticleId($article->getId()); 
     260 
     261Il Listato 8-10 mostra l'utilizzo dei getter generati; inoltre mostra anche come concatenare le chiamate ai metodi al modello. 
     262 
     263Listato 8-10 - Chiavi importate trasformate in getter speciali 
     264 
     265 
     266    [php] 
     267    // Relazione molti-a-molti 
     268    echo $comment->getArticle()->getTitle(); 
     269     => My first article 
     270    echo $comment->getArticle()->getContent(); 
     271     => This is my very first article. 
     272        Hope you enjoy it! 
     273     
     274    // Relazione uno-a-molti 
     275    $comments = $article->getComments(); 
     276 
     277Il metodo `getArticle()` restituisce un oggetto di tipo `Article`, che trae beneficio dall'accessorio `getTitle()`. Questo è molto meglio che fare il join manualmente, il che richiederebbe qualche linea di codice in più (cominciando dalla chiamata `$comment->getArticleId()`). 
     278 
     279La variabile `$comments` nel Listato 8-10 contiene un array di oggetti della classe `Comment`. Puoi mostrare il primo con `$comments[0]` oppure iterare al suo interno tramite un ciclo `foreach ($comments as $comment)`. 
     280 
     281>**NOTE** 
     282>Gli oggetti del modello sono definiti da un nome singolare per convenzione, ed ora ne puoi capire il motivo. La chiave importata definita nella tabella `blog_comment` causa la creazione di un metodo `getComments()` che prende il proprio nome aggiungendo una `s` al nome dell'oggetto `Comment`. Se tu avessi dato al modello il nome al plurale, avrebbe generato un metodo chiamato `getCommentss()` che non ha senso. 
     283 
     284### Salvare ed eliminare dati 
     285 
     286Chiamando il costruttore `new`, hai creato un nuovo oggetto, che però non è ancora salvato nella tabella `blog_article`. Anche modificare l'oggetto non ha alcuna implicazione sul database. Per poter memorizzare dati nel database, occorre chiamare il metodo `save()` dell'oggetto. 
     287 
     288 
     289    [php] 
     290    $article->save(); 
     291 
     292ORM è abbastanza intelligente da capire la relazione tra gli oggetti, per cui salvare l'oggetto `$article` significa che anche l'oggetto `$comment` viene salvato. Inoltre ORM sa anche se l'oggetto ha già un omologo nel database, per cui certe volte la chiamata `save()` porta ad una query di INSERT, mentre altre ad un UPDATE. La chiave primaria viene impostata automaticamente dal metodo `save()`, per cui dopo aver salvato l'oggetto ne puoi recuperare l'ID con `$article->getId()`. 
     293 
     294>**NOTE** 
     295>Puoi controllare se un oggetto è nuovo chiamando `isNew()`. Se inoltre desideri sapere se un oggetto sia stato modificato ed abbia bisogno di essere salvato, puoi chiamare `isModified()`. 
     296 
     297 
     298Leggendo i commenti ai tuoi articoli, potresti cambiare idea riguarda alla pubblicazione su Internet. E se non apprezzi l'ironia dei commentatori degli articoli, puoi facilmente cancellare un commento tramite il metodo `delete()`, come mostrato nel Listato 8-11. 
     299 
     300Listato 8-11 - Cancellare record dal database tramite il metodo delete() 
     301 
     302    [php] 
     303    foreach ($article->getComments() as $comment) 
     304    { 
     305      $comment->delete(); 
     306    } 
     307 
     308>**NOTE** 
     309>Anche dopo aver cancellato un oggetto con il metodo `delete()`, tale oggetto rimane disponibile fino alla fine della richiesta. Per sapere se un oggetto è stato eliminato dal database, chiama il metodo `isDeleted()`. 
     310 
     311### Recuperare record tramite chiave primaria 
     312 
     313Se conosci la chiave primaria di un record particolare, puoi usare il metodo `retrieveByPk()` della classe peer dell'oggetto relativo. 
     314 
     315    [php] 
     316    $article = ArticlePeer::retrieveByPk(7); 
     317 
     318Il file `schema.yml` definisce il campo `id` come chiave primaria della tabella `blog_article`, per cui questo statement restituisce effettivamente il record con ID 7. Dato che hai usato la chiave primaria, sai che verrà restituito esattamente un record; la variabile `$article` contiene un oggetto della classe `Article`. 
     319 
     320In qualche caso, una chiave primaria può essere composta da più di una colonna. In tali casi, il metodo `retrieveByPK()` accetta più parametri, uno per ogni colonna parte della chiave primaria. 
     321 
     322Puoi anche selezionare più oggetti tramite le loro chiavi primarie, con il metodo `retrieveByPKs()`, che si aspetta come parametro un array di chiavi primarie. 
     323 
     324 
     325### Recuperare record tramite Criteria 
     326 
     327Quando vuoi recuperare più di un record, devi utilizzare il metodo `doSelect()` della classe peer dell'oggetto. Ad esempio, per avere oggetti della classe `Article`, devi chiamare `ArticlePeer::doSelect()`. 
     328 
     329Il primo parametro del metodo `doSelect()` è un oggetto della classe `Criteria`, che è una semplice classe di definizione delle query costruita senza SQL nell'interesse dell'astrazione dal database. 
     330 
     331Un `Criteria` vuoto restituisce tutti gli oggetti della classe. Ad esempio, il Listato 8-12 mostra come ottenere tutti gli articoli. 
     332 
     333Listato 8-12 - Recuperare record con `doSelect()` -- Criteria vuoto 
     334 
     335    [php] 
     336    $c = new Criteria(); 
     337    $articles = ArticlePeer::doSelect($c); 
     338  
     339    // Si trasformerà nella seguente query SQL 
     340    SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT, 
     341           blog_article.CREATED_AT 
     342    FROM   blog_article; 
     343 
     344 
     345>**SIDEBAR** 
     346>Hydrating 
     347> 
     348>La chiamata a `::doSelect()` è molto più potente di una semplice query SQL. Tanto per cominciare, il codice SQL risultante è ottimizzato per il database in uso. Inoltre, i valori passati a `Criteria` vengono filtrati tramite escape per evitare rischi di SQL injection. Infine, il metodo restituisce un array di oggetti, invece di un result set. ORM si occupa di creare e popolare automaticamente oggetti basandosi sul result set. Questo processo si chiama Hydrating. 
     349 
     350Per una selezione più complicata hai bisogno dell'equivalente di WHERE, ORDER BY, GROUP BY ed altri operatori SQL. L'oggetto `Criteria` possiede parametri e metodi per tali operatori. Ad esempio, per recuperare tutti i commenti scritti da Steve ordinati per data, costruisci un `Criteria` come nel Listato 8-13. 
     351 
     352Listato 8-13 - Recuperare record con `doSelect()` -- Criteria con condizioni 
     353 
     354    [php] 
     355    $c = new Criteria(); 
     356    $c->add(CommentPeer::AUTHOR, 'Steve'); 
     357    $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); 
     358    $comments = CommentPeer::doSelect($c); 
     359     
     360    // Si trasformerà nella seguente query SQL 
     361    SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT, 
     362           blog_comment.CREATED_AT 
     363    FROM   blog_comment 
     364    WHERE  blog_comment.author = 'Steve' 
     365    ORDER BY blog_comment.CREATED_AT ASC; 
     366 
     367 
     368Le costanti passate come parametri ai metodi add() si riferiscono ai nomi delle proprietà. Sono nominati con la versione maiuscola del nome delle colonne. Ad esempio, per indirizzare la colonna `content` della tabella `blog_article`, usa la costante `ArticlePeer::CONTENT`. 
     369 
     370>**NOTE** 
     371>Perché utilizzare `CommentPeer::AUTHOR` invece di `blog_comment.AUTHOR`, che è in ogni caso il modo in cui verrà trasformata nella query in output? Supponi di dover cambiare il nome del campo dell'autore nel database in `contributor`. Se tu avessi usato `blog_comment.AUTHOR`, dovresti cambiare tutte le chiamate nel modello. D'altra parte, usando `CommentPeer::AUTHOR`, hai bisogno di cambiare solo il nome della colonna nel file `schema.yml`, mantenendo come `phpName` `AUTHOR`, e fare il rebuild del modello. 
     372 
     373La Tabella 8-1 confronta la sintassi SQL con quella di `Criteria`. 
     374 
     375Tabella 8-1 - Sintassi SQL e di `Criteria` 
     376 
     377SQL                                                          | Criteria 
     378------------------------------------------------------------ | ----------------------------------------------- 
     379`WHERE column = value`                                       | `->add(column, value);` 
     380`WHERE column <> value`                                      | `->add(column, value, Criteria::NOT_EQUAL);` 
     381**Altri operatori di comparazione**                          | 
     382`> , <`                                                      | `Criteria::GREATER_THAN, Criteria::LESS_THAN` 
     383`>=, <=`                                                     | `Criteria::GREATER_EQUAL, Criteria::LESS_EQUAL` 
     384`IS NULL, IS NOT NULL`                                       | `Criteria::ISNULL, Criteria::ISNOTNULL` 
     385`LIKE, ILIKE`                                                | `Criteria::LIKE, Criteria::ILIKE` 
     386`IN, NOT IN`                                                 | `Criteria::IN, Criteria::NOT_IN` 
     387**Altre parole chiave SQL**                                  | 
     388`ORDER BY column ASC`                                        | `->addAscendingOrderByColumn(column);` 
     389`ORDER BY column DESC`                                       | `->addDescendingOrderByColumn(column);` 
     390`LIMIT limit`                                                | `->setLimit(limit)` 
     391`OFFSET offset`                                              | `->setOffset(offset) ` 
     392`FROM table1, table2 WHERE table1.col1 = table2.col2`        | `->addJoin(col1, col2)` 
     393`FROM table1 LEFT JOIN table2 ON table1.col1 = table2.col2`  | `->addJoin(col1, col2, Criteria::LEFT_JOIN)` 
     394`FROM table1 RIGHT JOIN table2 ON table1.col1 = table2.col2` | `->addJoin(col1, col2, Criteria::RIGHT_JOIN)` 
     395 
     396 
     397>**TIP** 
     398>Il modo migliore per scoprire e capire i metodi disponibili nelle classi generate è quello di dare un'occhiata ai file `Base` nella cartella `lib/model/om/` dopo la generazione. I metodi sono abbastanza espliciti, ma se hai bisogno di più commenti imposta a `true` il parametro `propel.builder.addComments` nel file `config/propel.ini` e fai il rebuild del modello. 
     399 
     400Il Listato 8-14 mostra un altro esempio dell'utilizzo di `Criteria` con condizioni multiple. Recupera tutti i commenti di Steve ad articoli che contengono la parola "enjoy", ordinati per data. 
     401 
     402Listato 8-14 - Un altro esempio di recupero dati con doSelect() -- `Criteria` con condizioni multiple 
     403 
     404    [php] 
     405    $c = new Criteria(); 
     406    $c->add(CommentPeer::AUTHOR, 'Steve'); 
     407    $c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID); 
     408    $c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE); 
     409    $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); 
     410    $comments = CommentPeer::doSelect($c); 
     411     
     412    // Si trasformerà nella seguente query SQL 
     413    SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR, 
     414           blog_comment.CONTENT, blog_comment.CREATED_AT 
     415    FROM   blog_comment, blog_article 
     416    WHERE  blog_comment.AUTHOR = 'Steve' 
     417           AND blog_article.CONTENT LIKE '%enjoy%' 
     418           AND blog_comment.ARTICLE_ID = blog_article.ID 
     419    ORDER BY blog_comment.CREATED_AT ASC 
     420 
     421Proprio come SQL è di un linguaggio semplice che ti permette di costruire query complesse, anche l'oggetto Criteria può gestire condizioni a qualsiasi livello di complessità. Ma dato che molti sviluppatori pensano in SQL per poi trasformare la condizione in una logica orientata agli oggetti, `Criteria` può risultare ostico all'inizio. Il modo migliore per capirlo è tramite esempi. Il sito di symfony, ad esempio, ne è pieno e ti aiuterà a fare luce in diversi modi. 
     422 
     423In aggiunta al metodo `doSelect()`, ogni classe peer possiede un metodo `doCount()`, che restituisce (come intero) il numero di record che soddisfano i criteri passati come parametri. Dato che non c'è oggetto da restituire, in questo caso non viene effettuato il processo di Hydrating, così il metodo `doCount()` è più veloce di `doSelect()`. 
     424 
     425Le classi peer forniscono anche i metodi `doDelete()`, `doInsert()`, e `doUpdate()`, che si aspettano un `Criteria` come parametro. Tali metodi ti permettono di eseguire delle query di DELETE, INSERT, e UPDATE. Per maggiori informazioni su questi metodi Propel, controlla il codice delle classi peer del tuo modello. 
     426 
     427Infine, se ti serve semplicemente il primo oggetto restituito, sostituisci `doSelect()` con `doSelectOne()`. Questo potrebbe essere ad esempio il caso in cui sai a priori che `Criteria` restituirà solo un record, ed il vantaggio di tale metodo è che restituisce un solo oggetto invece di un array di oggetti. 
     428 
     429>**TIP** 
     430>Quando una query `doSelect()` restituisce un gran numero di risultati, potresti volerne visualizzare solo una parte. Symfony fornisce una classe per la paginazione chiamata sfPropelPager, che automatizza tale processo. Controlla la documentazione della paginazione su [http://www.symfony-project.org/cookbook/1_1/pager](http://www.symfony-project.org/cookbook/1_1/pager) per maggiori dettagli in merito. 
     431 
     432### Eseguire query SQL grezze 
     433 
     434Potrebbe capitare di non voler recuperare oggetti, ma solo un risultato sintetico calcolato dal database. Ad esempio, per avere l'ultima data di creazione fra tutti gli articoli, non avrebbe senso recuperarli tutti ed iterare nell'array. Sarebbe meglio chiedere direttamente il dato al db, ed evitare il processo di Hydrating. 
     435 
     436D'altra parte, non vuoi utilizzare direttamente comandi PHP per la gestione del database, così da perdere il livello di astrazione. Questo significa che devi scavalcare ORM (Propel) ma non l'astrazione del database (Creole). 
     437 
     438Eseguire query con Creole richiede: 
     439  1. Avere una connessione al db. 
     440  2. Costruire una stringa contenente la query. 
     441  3. Costruire uno statement dai punti precedenti. 
     442  4. Iterare nel result set restituito dall'esecuzione dello statement. 
     443 
     444Se questo ti pare troppo complicato, probabilmente il Listato 8-15 ti aiuterà a capire. 
     445 
     446Listato 8-15 - Query SQL con Creole 
     447 
     448    [php] 
     449    $connection = Propel::getConnection(); 
     450    $query = 'SELECT MAX(%s) AS max FROM %s'; 
     451    $query = sprintf($query, ArticlePeer::CREATED_AT, ArticlePeer::TABLE_NAME); 
     452    $statement = $connnection->prepareStatement($query); 
     453    $resultset = $statement->executeQuery(); 
     454    $resultset->next(); 
     455    $max = $resultset->getInt('max'); 
     456 
     457Proprio come selezioni Propel, le query Creole sono un po' ostiche quando cominci ad usarle; ancora una volta, l'aiuto migliore arriverà dagli esempi esistenti. 
     458 
     459>**CAUTION** 
     460>Se sei tentato di bypassare questo processo ed accedere al database direttamente, rischi di perdere la sicurezza e l'astrazione forniti da Creole. Utilizzare la sintassi di Creole richiede più tempo, ma ti da la garanzia di efficienza, portabilità e sicurezza della tua applicazione. Questo è particolarmente vero per query con parametri provenienti da sorgenti sconosciute (come un utente Internet). Creole si occupa della sicurezza del tuo database, e non utilizzarlo significa essere a rischio di attacchi di SQL injection. 
     461 
     462### Utilizzare le colonne speciali per le date 
     463 
     464Di solito, quando una tabella ha una colonna chiamata `created_at`, è utilizzata per memorizzare un timestamp della data in cui il record è stato creato. Lo stesso vale per colonne chiamate `updated_at`, che vengono aggiornate all'ora corrente al momento in cui il record viene aggiornato. 
     465 
     466La buona notizia è che symfony riconoscerà automaticamente i nomi di tali colonne e gestirà gli aggiornamenti al posto tuo. Non hai bisogno di impostare i valori dei campi `created_at` e `updated_at`; come mostrato nel Listato 8-16, ciò avverrà automaticamente. Lo stesso succede per le colonne che si chiamano `created_on` e `updated_on`. 
     467 
     468Listato 8-16 - Le colonne `created_at` e `updated_at` vengono aggiornate automaticamente 
     469 
     470    [php] 
     471    $comment = new Comment(); 
     472    $comment->setAuthor('Steve'); 
     473    $comment->save(); 
     474     
     475    // Mostra la data di creazione 
     476    echo $comment->getCreatedAt(); 
     477      => [date of the database INSERT operation] 
     478 
     479In aggiunta, i getter per le date accettano un parametro per il formato: 
     480 
     481    [php] 
     482    echo $comment->getCreatedAt('Y-m-d'); 
     483 
     484>**SIDEBAR** 
     485>Refactoring al livello dati 
     486>  
     487>Quando svilupperai un progetto con symfony, spesso comincerai con lo scrivere la domain logic nelle azioni. Ma le query al database e la manipolazione del modello non dovrebbero essere memorizzate nel layer del controller. Tutta la logica relativa ai dati dovrebbe essere spostata nel layer del modello. Ogni qualvolta avrai bisogno della stessa richiesta in più di un punto nelle tue azioni, pensa all'idea di trasferire il codice relativo nel modello. Fare ciò aiuta a mantenere le azioni corte e facili da leggere. 
     488> 
     489>Ad esempio, immagina il codice necessario in un blog per recuperare i dieci articoli più popolari per un dato tag (passato come parametro). Tale codice non dovrebbe essere in un'azione, ma nel modello. Se avessi bisogno di mostrare tale lista in un template, l'azione dovrebbe essere semplicemente:</p> 
     490> 
     491>     [php] 
     492>     public function executeShowPopularArticlesForTag($request) 
     493>     { 
     494>       $tag = TagPeer::retrieveByName($request->getParameter('tag')); 
     495>       $this->foward404Unless($tag); 
     496>       $this->articles = $tag->getPopularArticles(10); 
     497>     } 
     498> 
     499>L'azione crea un oggetto della classe `Tag` dal parametro di richiesta. Quindi tutto il codice necessario alla query è situato nel metodo `getPopularArticles()` di tale classe. Così l'azione è più leggibile, ed il codice nel modello è facilmente riutilizzabile in un'altra azione. 
     500> 
     501>Spostare il codice in un luogo più appropriato fa parte della tecnica di refactoring. Se lo fai spesso, il tuo codice sarà più facile da manutenere e da comprendere per gli altri sviluppatori. Una buona regola per capire quando c'è bisogno di spostare del codice, è quando una azione contiene più di dieci linee di codice PHP. 
     502 
     503 
     504Connessioni al database 
     505----------------------- 
     506 
     507Il modello dei dati è indipendente dal database utilizzato, ma sicuramente ne userai uno. Le informazioni di cui symfony necessita per eseguire richieste al db sono il nome del database stesso, il tipo e utente e password per il collegamento. Tali impostazioni possono essere configurate passando un data source name (DSN) al task configure:database 
     508 
     509    php symfony --app=frontend configure:database "mysql://login:passwd@localhost/blog" 
     510 
     511Per ogni ambiente, puoi definire più di una connessione. Ogni connessione si riferisce ad uno schema etichettato con lo stesso nome. Il nome predefinito per la connessione predefinita è `propel` e si referisce allo schema propel del Listato 8-3. L'opzione `name` consente di creare un'altra connessione: 
     512 
     513    php symfony --name=main configure:database "mysql://login:passwd@localhost/blog" 
     514 
     515Puoi anche inserire queste impostazioni di connnessione manualmente nel file `databases.yml` che si trova nella cartella `config/` Il Listato 8-17 mostra un esempio di tale file ed il Listato 8-18 mostra lo stesso esempio con la notazione estesa. 
     516 
     517Listato 8-17 - Impostazioni di connessione in forma abbreviata 
     518 
     519    [php] 
     520    all: 
     521      propel: 
     522        class:          sfPropelDatabase 
     523        param: 
     524          dsn:          mysql://login:passwd@localhost/blog 
     525 
     526Listato 8-18 - Esempio di impostazioni di connessione al database, in `myproject/config/databases.yml` 
     527 
     528    [php] 
     529    prod: 
     530      propel: 
     531        param: 
     532          host:               mydataserver 
     533          username:           myusername 
     534          password:           xxxxxxxxxx 
     535     
     536    all: 
     537      propel: 
     538        class:                sfPropelDatabase 
     539        param: 
     540          phptype:            mysql     # Tipo di database 
     541          hostspec:           localhost 
     542          database:           blog 
     543          username:           login 
     544          password:           passwd 
     545          port:               80 
     546          encoding:           utf-8     # Charset di default per la creazione delle tabelle 
     547          persistent:         true      # Usa connessioni persistenti 
     548 
     549Le impostazioni di connessione dipendono dall'ambiente. Puoi definire impostazioni differenti per gli ambienti `prod`, `dev`, `test` o qualsiasi altro ambiente della tua applicazione. Un'applicazione può anche sovrascrivere tali impostazioni, ad esempio impostandone valori diversi nel file `apps/myapp/config/databases.yml`. Ad esempio, puoi utilizzare questo approccio per avere diverse politiche di sicurezza per il frontend ed il backend della tua applicazione, e definire diversi utenti (con diversi privilegi) per questa gestione. 
     550 
     551Puoi definire varie connessioni per ogni ambiente. Ogni connessione si riferisce ad uno schema con lo stesso nome. Nell'esempio del Listato 8-17, la connessione propel si riferisce allo schema `propel` del Listato 8-3. 
     552 
     553I valori di `phptype` permessi sono quelli dei tipi di database supportati da Creole: 
     554 
     555  * `mysql` 
     556  * `sqlserver` 
     557  * `pgsql` 
     558  * `sqlite` 
     559  * `oracle` 
     560 
     561`hostspec`, `database`, `username`, e `password` sono le solite impostazioni di connessione. Puoi anche scriverle con una sintassi più breve (data source name, o DSN). Il Listato 8-18 è equivalente alla sezione `all:` del Listato 8-17. 
     562 
     563Se usi un database SQLite, il parametro `hostspec` deve essere impostato sul percorso del file che contiene il database. Ad esempio, se il database del tuo blog è nel file `data/blog.db`, il file `databases.yml` deve essere impostato come nel Listato 8-19. 
     564 
     565Listato 8-19 - Impostazioni di connessione per SQLite con il database in un file 
     566 
     567    [php] 
     568    all: 
     569          propel: 
     570            class:          sfPropelDatabase 
     571            param: 
     572              phptype:  sqlite 
     573              database: %SF_DATA_DIR%/blog.db 
     574 
     575Estendere il modello 
     576-------------------- 
     577 
     578I metodi del modello generati sono utili ma spesso non sufficienti. Dato che implementerai la tua business logic, avrai bisogno di estendere il modello, creando i tuoi metodi o facendo l'override di quelli esistenti. 
     579 
     580 
     581### Aggiungere nuovi metodi 
     582 
     583Puoi aggiungere nuovi metodi alle classi del modello vuote generate nella cartella `lib/model/`. Utilizza `$this` per chiamare metodi dell'oggetto corrente, e `self::` per chiamare metodi statici della classe corrente. Ricorda che le classi personalizzate ereditano dalle classi `Base` situate nella cartella `lib/model/om/`. 
     584 
     585Ad esempio, per l'oggetto `Article` del Listato 8-3, puoi aggiungere un metodo magico `__toString()` cosicché stampare un oggetto di tale classe ne visualizzerà il titolo, come mostrato nel Listato 8-20. 
     586 
     587Listato 8-20 - Personalizzare il modello, in `lib/model/Article.php` 
     588 
     589    [php] 
     590    <?php 
     591     
     592    class Article extends BaseArticle 
     593    { 
     594      public function __toString() 
     595      { 
     596        return $this->getTitle();  // getTitle() è ereditato da BaseArticle 
     597      } 
     598    } 
     599 
     600 
     601Puoi anche estendere le classi peer, ad esempio per avere un metodo che recupera tutti gli articoli ordinati per data di creazione, come mostrato nel Listato 8-21. 
     602 
     603Listato 8-21 - Personalizzare il modello, in `lib/model/ArticlePeer.php` 
     604 
     605    [php] 
     606    <?php 
     607     
     608    class ArticlePeer extends BaseArticlePeer 
     609    { 
     610      public static function getAllOrderedByDate() 
     611      { 
     612        $c = new Criteria(); 
     613        $c->addAscendingOrderByColumn(self:CREATED_AT); 
     614        return self::doSelect($c); 
     615      
     616      } 
     617    } 
     618 
     619I nuovi metodi saranno disponibili come quelli generati, come mostrato nel Listato 8-22. 
     620 
     621listato 8-22 - Usare nuovi metodi è come usare quelli generati 
     622 
     623    [php] 
     624    foreach (ArticlePeer::getAllOrderedByDate() as $article) 
     625    { 
     626      echo $article;      // Chiama il metodo magico __toString() 
     627    } 
     628 
     629### Fare l'override di metodi esistenti 
     630 
     631Se qualcuno dei metodi generati nelle classi `Base` non soddisfano i tuoi bisogni, ne puoi fare l'override nelle tue classi. Devi solo essere sicuro di usare gli stessi argomenti. 
     632 
     633Ad esempio, `$article->getComments()` restituisce un array di oggetti `Comment`, senza alcun ordine particolare. Se vuoi avere il risultato ordinato per data di creazione, con l'ultimo commento inserito che appare come primo, devi fare l'override del metodo `getComments()`, come mostrato nel Listato 8-23. Fai attenzione al fatto che il metodo originale `getComments()` (situato nel file `lib/model/om/BaseArticle.php`) si aspetta come parametri un criterio ed una connessione, per cui anche il tuo metodo deve fare lo stesso. 
     634 
     635Listato 8-23 - Override di metodi esistenti, in `lib/model/Article.php` 
     636 
     637    [php] 
     638    public function getComments($criteria = null, $con = null ) 
     639    { 
     640      // Gli oggetti in PHP5 sono passati per riferimento, quindi per evitare di modificare l'originale lo devi clonare 
     641      $criteria = clone $criteria; 
     642      $criteria->addDescendingOrderByColumn(ArticlePeer::CREATED_AT); 
     643      
     644      return parent::getComments($criteria, $con); 
     645    } 
     646 
     647Il metodo personalizzato alla fine chiama il genitore della classe Base, e questa è una buona pratica, anche se in generale puoi scavalcarla completamente e restituire il risultato che preferisci. 
     648 
     649 
     650### Utilizzare behaviors del modello 
     651 
     652Alcune modifiche del modello sono generiche e possono essere riutilizzate. Ad esempio, i metodi per rendere un oggetto ordinabile od un lock ottimistico per prevenire conflitti fra salvataggi concorrenti sono estensioni generiche che possono venire aggiunte a diverse classi. 
     653 
     654Symfony pacchettizza tali estensioni in behaviors. Essi sono classi esterne che forniscono metodi addizionali alle classi del modello. Esse contengono già degli hook, e symfony sa come estenderli tramite `sfMixer` (per dettagli vedi il Capitolo 17). 
     655 
     656Per abilitare i behaviors nelle classi del tuo modello, devi modificare un'impostazione nel file `config/propel.ini`: 
     657 
     658    [php] 
     659    propel.builder.AddBehaviors = true     // Il valore di default è false 
     660 
     661Non ci sono behavior pacchettizzati di default in symfony, ma sono installabili tramite plugin. Una volta installato, lo puoi assegnare ad una classe tramite una riga di codice. Ad esempio, se installi nella tua applicazione `sfPropelParanoidBehaviorPlugin`, puoi estendere una classe Article con questo behavior aggiungendo alla fine del file `Article.class.php`: 
     662 
     663    [php] 
     664    sfPropelBehavior::add('Article', array( 
     665      'paranoid' => array('column' => 'deleted_at') 
     666    )); 
     667 
     668Dopo aver fatto il rebuilding del modello, gli oggetti `Article` eliminati rimarranno nel database, invisibili alle query eseguite tramite ORM, a meno di disabilitare temporaneamente il behavior tramite `sfPropelParanoidBehavior::disable()`. 
     669 
     670'''Nuovo in symfony 1.1''': In alternativa, puoi anche dichiarare i comportamenti direttamente in `schema.yml`, elencandoli sotto la chiave `_behaviors` (vedi Listato 8-34 sotto). 
     671 
     672Controlla nel wiki la lista dei plugin di symfony per vedere quali sono i behavior esistenti ([http://www.symfony-project.com/trac/wiki/SymfonyPlugins#Propelbehaviorplugins](http://www.symfony-project.com/trac/wiki/SymfonyPlugins#Propelbehaviorplugins)). Ognuno ha la propria guida di installazione ed utilizzo. 
     673 
     674Estendere la sintassi dello schema 
     675---------------------------------- 
     676 
     677Un file `schema.yml` può essere semplice, come mostrato nel Listato 8-3. Ma i modelli relazionali spesso sono complicati. Questo è il motivo per cui lo schema possiede una sintassi estensiva che permetti di gestire quasi tutti i casi. 
     678 
     679 
     680### Attributi 
     681 
     682Connessioni e tabelle possono avere attributi specifici, come mostrato nel Listato 8-24. Essi vengono impostati sotto una chiave `_attributes`. 
     683 
     684Listato 8-24 - Attributi per connessioni e tabelle 
     685 
     686    [php] 
     687    propel: 
     688      _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model } 
     689      blog_article: 
     690        _attributes: { phpName: Article } 
     691 
     692Potresti volere che lo schema venga validato prima della generazione del codice. Per fare ciò, disabilita l'attributo `noXSD` per la connessione. Essa supporta anche l'attributo `defaultIdMethod`. Se alcun metodo per la generazione di ID viene fornito, verrà utilizzato quello di default del database; ad esempio, `autoincrement` per MySQL o `sequences` per PostgreSQL. L'altro valore possibile è `none`. 
     693 
     694L'attributo `package` è come un namespace; determina il percorso nel quale vengono generate le classi. Il default è `lib/model/`, ma puoi cambiarlo per organizzare il tuo modello in sottopacchetti. Ad esempio, se non vuoi tenere le classi che contengono il core business con quelle delle statistiche nella stessa cartella, definisci due schemi con i pacchetti `lib.model.business` e `lib.model.stats`. 
     695 
     696Hai già visto l'attributo della tabella `phpName`, usato per mapparla con le classi generate. 
     697 
     698Anche le tabelle che contengono contenuti localizzati (ovvero diverse versioni degli stessi contenuti, per l'internazionalizzazione) posseggono due attributi ulteriori (vedi il Capitolo 13 per i dettagli), come mostrato nel Listato 8-25. 
     699 
     700Listato 8-25 - Attributi per le tabelle i18N 
     701 
     702    [php] 
     703    propel: 
     704      blog_article: 
     705        _attributes: { isI18N: true, i18nTable: db_group_i18n } 
     706 
     707>**SIDEBAR** 
     708> 
     709>Gestire schemi multipli 
     710>  
     711>Puoi avere più di uno schema per applicazione. Symfony prende in considerazione tutti i file della cartella `config/` il cui nome finisce con `schema.yml` oppure `schema.xml`. Se la tua applicazione ha molte tabelle, o se alcune di esse non condividono la stessa connessione, troverai questo approccio molto utile. Considera i due schemi seguenti: 
     712> 
     713>      // In config/business-schema.yml 
     714>      propel: 
     715>        blog_article: 
     716>          _attributes: { phpName: Article } 
     717>        id: 
     718>        title: varchar(50) 
     719> 
     720>      // In config/stats-schema.yml 
     721>      propel: 
     722>        stats_hit: 
     723>          _attributes: { phpName: Hit } 
     724>        id: 
     725>        resource: varchar(100) 
     726>        created_at: 
     727> 
     728>Entrambi gli schemi usano la stessa connessione (`propel`), e le classi `Article` e `Hit` saranno generate nella stessa cartella `lib/model/`. Tutto sarà come se avessi scritto un unico schema. 
     729>Puoi anche avere diversi schemi che usano diverse connessioni (ad esempio, `propel` e `propel_bis`, da definire in `databases.yml`) ed organizzare le classi generate in sottocartelle: 
     730> 
     731>      // In config/business-schema.yml 
     732>      propel: 
     733>        blog_article: 
     734>          _attributes: { phpName: Article, package: lib.model.business } 
     735>        id: 
     736>        title: varchar(50) 
     737> 
     738>      // In config/stats-schema.yml 
     739>      propel_bis: 
     740>        stats_hit: 
     741>          _attributes: { phpName: Hit, package.lib.model.stat } 
     742>        id: 
     743>        resource: varchar(100) 
     744>        created_at: 
     745> 
     746>Molte applicazioni usano più di uno schema. In particolare, qualche plugin possiede il proprio schema e sistema di pacchetti per evitare di creare confusione con le tue classi (vedi il Capitlo 17 per dettagli). 
     747 
     748### Dettagli delle colonne 
     749 
     750La sintassi di base ti da due scelte: lasciare che symfony capisca dal nome delle colonne le loro caratteristiche (lasciando un valore vuoto) o definirne il tipo, come mostrato nel Listato 8-26. 
     751 
     752Listato 8-26 - Attributi di base delle colonne 
     753 
     754    [php] 
     755    propel: 
     756      blog_article: 
     757        id:                 # Lascia fare il lavoro a symfony 
     758        title: varchar(50)  # Specifica tu stesso il tipo 
     759 
     760Ma tu puoi definire molto di più per una colonna. Se lo fai, dovrai utilizzare la sintassi degli array associativi, come mostrato nel Listato 8-27. 
     761 
     762Listato 8-27 - Attributi complessi per le colonne 
     763 
     764    [php] 
     765    propel: 
     766      blog_article: 
     767        id:       { type: integer, required: true,primaryKey: true, autoIncrement: true } 
     768        name:     { type: varchar(50), default: foobar, index: true } 
     769        group_id: { type: integer, foreignTable: db_group,foreignReference: id, onDelete: cascade } 
     770 
     771I parametri delle colonne sono i seguenti: 
     772 
     773  * `type`: tipo di colonna. La scelta è fra `boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(size), longvarchar, date, time, timestamp, bu_date, bu_timestamp, blob`, e `clob`. 
     774  * `required`: booleano. Se vuoi che la colonna sia obbligatoria impostalo su `true`. 
     775  * `default`: valore di default. 
     776  * `primaryKey`: booleano. Impostalo su `true` se vuoi che la colonna sia parte della chiave primaria. 
     777  * `autoIncrement`: booleano. Impostalo su `true` per colonne di tipo `integer` che hanno bisogno di valori auto-increment. 
     778  * `sequence`: per database che utilizzano sequenze per colonne `autoIncrement` (come PostgreSQL e Oracle). 
     779  * `index`: booleano. Impostalo su `true` se vuoi un indice semplice o a `unique` se vuoi un indice unico sulla colonna. 
     780  * `foreignTable`: nome di tabella usato per creare chiavi importate su un'altra tabella. 
     781  * `foreignReference`: nome della colonna relativa se è stata definita una chiave importata con `foreignTable`. 
     782  * `onDelete`: determina l'azione da eseguire quando viene eliminato un record dalla tabella. Se impostato su `setnull`, il record della chiave importata è impostata a `null`. Se impostato su `cascade`, il record viene eliminato. Se il db non supporta tali comportamenti, ORM li emula. Questo attributo è rilevante solo per colonne che hanno impostato una `foreignTable` e una `foreignReference`. 
     783  * `isCulture`: booleano. Impostalo su `true` per colonne in tabelle di localizzazione (v. Capitolo 13). 
     784 
     785### Chiavi importate 
     786 
     787Come alternativa agli attributi `foreignTable` e `foreignReference`, puoi aggiungere chiavi importate in una tabella sotto la chiave `_foreignKeys:`. Lo schema del Listato 8-28 creerà una chiave importata sulla colonna `user_id`, che si corrisponde alla colonna `id` della tabella `blog_user`. 
     788 
     789Listato 8-28 - Sintassi alternativa per chiavi importate 
     790 
     791    [php] 
     792    propel: 
     793      blog_article: 
     794        id: 
     795        title:   varchar(50) 
     796        user_id: { type: integer } 
     797        _foreignKeys: 
     798          - 
     799            foreignTable: blog_user 
     800            onDelete:     cascade 
     801            references: 
     802              - { local: user_id, foreign: id } 
     803 
     804La sintassi alternativa è utile per riferimenti multipli di chiavi importate, e per dargli un nome, come mostrato nel Listato 8-29. 
     805 
     806Listato 8-29 - Sintassi alternativa per chiavi importate applicata a riferimenti multipli 
     807 
     808    [php] 
     809     _foreignKeys: 
     810          my_foreign_key: 
     811            foreignTable:  db_user 
     812            onDelete:      cascade 
     813            references: 
     814              - { local: user_id, foreign: id } 
     815              - { local: post_id, foreign: id } 
     816 
     817### Indici 
     818 
     819Come alternativa all'attributo `index`, puoi aggiungere indici ad una tabella sotto la chiave `_indexes:`. Per definire indici unici, devi usare invece l'header `_uniques:`. Il Listato 8-30 mostra la sintassi alternativa per gli indici. 
     820 
     821Listato 8-30 - Sintassi alternative per gli indici 
     822 
     823    [php] 
     824    propel: 
     825      blog_article: 
     826        id: 
     827        title:            varchar(50) 
     828        created_at: 
     829        _indexes: 
     830          my_index:       [title, user_id] 
     831        _uniques: 
     832          my_other_index: [created_at] 
     833 
     834La sintassi alternativa è utile per indici su più colonne. 
     835 
     836### Colonne vuote 
     837 
     838Quando incontra una colonna senza valore, symfony aggiunge magicamente un valore. Il Listato 8-31 ne mostra un esempio. 
     839 
     840Listato 8-31 - Dettagli di colonne dedotti dal loro nome 
     841 
     842    [php] 
     843    // Colonne vuote chiamate id sono considerate chiavi primarie 
     844    id:         { type: integer, required: true, primaryKey: true, autoIncrement: true } 
     845     
     846    // Colonne vuote chiamate XXX_id sono considerate chiavi importate 
     847    foobar_id:  { type: integer, foreignTable: db_foobar, foreignReference: id } 
     848     
     849    // Colonne vuote chiamate created_at, updated at, created_on e updated_on 
     850    // sono considerate date e automaticamente vengono impostate di tipo timestamp 
     851    created_at: { type: timestamp } 
     852    updated_at: { type: timestamp } 
     853 
     854Per le chiavi importate, symfony cercherà una tabella che abbia il `phpName` uguale alla prima parte del nome della chiave, e se la trova, ne imposterà il nome come `foreignTable`. 
     855 
     856 
     857### Tabelle i18N 
     858 
     859Symfony supporta l'internazionalizzazione del contenuto nelle tabelle relative. Ciò significa che quando hai del contenuto soggetto a internazionalizzazione, esso viene memorizzato in due tabelle separate: una con colonne invariate, e un'altra con colonne internazionalizzate. 
     860 
     861Nel file `schema.yml`, tutto ciò è implicito quando chiami una tabella `foobar_i18n`. Ad esempio, lo schema mostrato nel Listato 8-32 sarà completato automaticamente con colonne e attributi in modo da far funzionare il meccanismo di internazionalizzazione. Internamente, symfony lo capirà come se fosse stato scritto come nel Listato 8-33. Il Capitolo 13 fornirà maggiori informazioni riguardo l'internazionalizzazione. 
     862 
     863Listato 8-32 - Meccanismo i18N implicito 
     864 
     865    [php] 
     866    propel: 
     867      db_group: 
     868        id: 
     869        created_at: 
     870     
     871      db_group_i18n: 
     872        name:        varchar(50) 
     873 
     874Listato 8-33 - Meccanismo i18N esplicito 
     875 
     876    [php] 
     877    propel: 
     878      db_group: 
     879        _attributes: { isI18N: true, i18nTable: db_group_i18n } 
     880        id: 
     881        created_at: 
     882     
     883      db_group_i18n: 
     884        id:       { type: integer, required: true, primaryKey: true,foreignTable: db_group, foreignReference: id, onDelete: cascade } 
     885        culture:  { isCulture: true, type: varchar(7), required: true,primaryKey: true } 
     886        name:     varchar(50) 
     887 
     888### Comportamenti (nuovo in symfony 1.1) 
     889 
     890I comportamenti sono modificatori del modello forniti da plugin che aggiungono nuove capacità alle tue classi Propel. Il Capitolo 17 spiega di più sui comportamenti. Puoi definire i comportamenti nell schema, elencandoli per ogni tabella, insieme ai loro parametri, sotto la chiave `_behaviors`. Il Listato 8-34 dà un esempio di estensione della classe `BlogArticle` con il comportamento `paranoid behavior`. 
     891 
     892Listato 8-34 - Dichiarazione dei Comportamenti 
     893 
     894    {{{ propel: blog_article: title: varchar(50) _behaviors: paranoid: { column: deleted_at } }}} 
     895 
     896### Oltre schema.yml: schema.xml 
     897 
     898Di fatto, il formato di schema.yml è interno a symfony. Quando chiami un comando propel:, symfony in effetti traduce questo file in `generated-schema.xml`, che è il tipo di file che Propel si aspetta per poter eseguire operazioni sul modello. 
     899 
     900Il file `schema.xml` contiene le stesse informazioni del suo equivalente YAML. Ad esempio, il Listato 8-3 viene convertito nel file xml mostrato nel Listato 8-35. 
     901 
     902Listato 8-35 - Esempio di `schema.xml`, corrispondente al Listato 8-3 
     903 
     904    [xml] 
     905    <?xml version="1.0" encoding="UTF-8"?> 
     906     <database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model"> 
     907        <table name="blog_article" phpName="Article"> 
     908          <column name="id" type="integer" required="true" primaryKey="true"autoIncrement="true" /> 
     909          <column name="title" type="varchar" size="255" /> 
     910          <column name="content" type="longvarchar" /> 
     911          <column name="created_at" type="timestamp" /> 
     912        </table> 
     913        <table name="blog_comment" phpName="Comment"> 
     914          <column name="id" type="integer" required="true" primaryKey="true"autoIncrement="true" /> 
     915          <column name="article_id" type="integer" /> 
     916          <foreign-key foreignTable="blog_article"> 
     917            <reference local="article_id" foreign="id"/> 
     918          </foreign-key> 
     919          <column name="author" type="varchar" size="255" /> 
     920          <column name="content" type="longvarchar" /> 
     921          <column name="created_at" type="timestamp" /> 
     922        </table> 
     923     </database> 
     924 
     925La descrizione del formato di `schema.xml` può essere trovata nella documentazione e nella sezione "Getting Started" del sito di Propel (http://propel.phpdb.org/docs/user_guide/chapters/appendices/AppendixB-SchemaReference.html). 
     926 
     927Il formato YAML è stato pensato per mantenere gli schemi facili da leggere e scrivere, ma il compromesso è il fatto che un schema molto complesso non può essere descritto in un file `schema.yml`. D'altra parte, il formato XML permette una descrizione completa, per ogni complessità, ed include impostazioni specifiche dei commercianti di database, ereditarietà delle tabelle e così via. 
     928 
     929In effetti symfony capisce schemi scritti in XML. Così, se il tuo schema è troppo complesso per la sintassi YAML, se hai già uno schema XML esistente o se hai già familiarità con la sintassi XML di Propel, non devi per forza scriver in YAML. Metti il tuo `schema.xml` nella cartella `config/`, fai il rebuild del modello e via. 
     930 
     931>**SIDEBAR** 
     932>Propel in symfony 
     933>  
     934>Tutti i dettagli forniti in questo capitolo non sono tanto relativi a symfony, quanto piuttosto a Propel. Esso è il livello di astrazione oggetto/relazione preferito in symfony, ma ne puoi scegliere uno alternativo. Comunque, symfony lavora molto trasparentemente con Propel, per le seguenti ragioni: 
     935>Tutte le classi del modello e quelle `Criteria` sono autoloading. Appena le usi, symfony si occuperà di tutto e tu non avrai bisogni di inclusioni. In symfony, Propel non ha bisogno di essere lanciato o inizializzato. Quando un oggetto usa Propel, la libreria inizializza se stessa. Alcuni plugin utilizzano oggetti Propel come parametri per raggiungere un alto livello di esecuzione (come la paginazione o i filtri). Gli oggetti Propel forniscono una rapida generazione di backend per la tua applicazione (v. Capitolo 14 per dettagli). Lo schema è più veloce da scrivere utilizzando la sintassi YAML. 
     936>Infine, Propel è indipendente dal database utilizzato, come symfony. 
     937 
     938 
     939Non creare il modello due volte 
     940------------------------------- 
     941 
     942Il prezzo da pagare solitamente per l'utilizzo di un ORM è il fatto che devi definire la struttura dei dati due volte: una per il database e una per il modello. Fortunatamente, symfony mette a disposizione comandi per generarne uno sulla base dell'altro, in modo da evitare di fare lo stesso lavoro due volte. 
     943 
     944 
     945### Costruire la struttura del database in SQL basandosi su uno schema esistente 
     946 
     947Se cominci la tua applicazione scrivendo il file `schema.yml`, symfony può generare una query che crea le tabelle direttamente dal modello YAML. Per fare ciò, scrivi il seguente comando dalla shell (ricorda di posizionarti prima nella cartella del tuo progetto): 
     948 
     949    > symfony propel:build-sql 
     950 
     951Verrà creato un file chiamato `lib.model.schema.sql` dentro la cartella `myproject/data/sql/`. Nota che il codice così generato sarà ottimizzato per il tipo di database specificato dal parametro `phptype` del file `propel.ini`. 
     952 
     953Puoi usare il file schema.sql per costruire direttamente le tabelle. Ad esempio, in MySQL puoi digitare: 
     954 
     955    > mysqladmin -u root -p create blog 
     956    > mysql -u root -p blog < data/sql/lib.model.schema.sql 
     957 
     958Il codice SQL generato può essere anche utile per ricostruire il db in un altro ambiente, o per cambiare DBMS. Se le impostazioni di connessione sono definite correttamente nel file `propel.ini`, puoi anche usare il comando `symfony propel:insert-sql` per farlo automaticamente. 
     959 
     960>**CAUTION** 
     961>La linea di comando mette a disposizione anche delle opzioni per popolare il db tramite file di testo. Nel Capitolo 16 sono fornite maggiori informazioni riguardo il task `propel:data-load` ed i file accessori di YAML. 
     962 
     963### Generare un modello YAML da un database esistente 
     964 
     965Symfony può utilizzare il layer di accesso al database di Creole per generare un file `schema.yml` da un db esistente, grazie all'introspezione (la capacità dei database di capire la struttura delle proprie tabelle). Questo diventa particolarmente utile quando devi fare reverse-engineering, o se preferisci lavorare sul db prima che sul modello. 
     966 
     967Per fare ciò, devi chiamare il comando `propel:build-schema`, dopo aver controllato di avere impostato correttamente tutti i settaggi relativi alla connessione nel file `propel.ini`: 
     968 
     969    > symfony propel:build-schema 
     970 
     971Un file `schema.yml` nuovo di zecca generato dalla struttura del database sarà situato nella cartella `config/`. Puoi costruire il tuo modello partendo da questo schema. 
     972 
     973I comandi di generazione dello schema sono molto potenti e ti permettono di aggiungere molto informazioni relative al database. Ma dato che YAML non gestisce tali informazioni, in questi casi devi utilizzare il formato XML. Puoi farlo semplicemente aggiungendo un parametro `xml` al comando `build-schema`: 
     974 
     975    > symfony propel:build-schema xml 
     976 
     977Così invece di generare uno `schema.yml`, creerai un file xml pienamente compatibile con Propel, contenente tutte le informazioni sul vendor. Ma fai attenzione al fatto che i file xml tendono ad essere prolissi e difficili da leggere. 
     978 
     979>**SIDEBAR** 
     980>La configurazione di propel.ini 
     981> 
     982>I comandi `propel:build-sql` e `propel:build-schema` non usano le impostazioni di connessione specificate in `databases.yml`. Essi utilizzano invece le impostazioni memorizzate nel file `propel.ini` situato nella cartella `config/`: 
     983> 
     984>      propel.database.createUrl = mysql://login:passwd@localhost 
     985>      propel.database.url       = mysql://login:passwd@localhost/blog 
     986> 
     987>Tale file contiene le impostazioni necessarie al generatore di Propel per ottenere classi compatibili con symfony. La maggior parte di tali settaggi sono interni e non molto interessanti per l'utente, a parte alcuni: 
     988> 
     989>      // Le classi Base sono autocaricate in symfony 
     990>      // Imposta questo a true per usare invece degli statement include_once 
     991>      // (Piccolo impatto negativo sulle performance) 
     992>      propel.builder.addIncludes = false 
     993> 
     994>      // Le classi generate non sono commentate per default 
     995>      // Imposta questo a true per aggiungere commenti alle classi Base 
     996>      // (Piccolo impatto negativo sulle performance) 
     997>      propel.builder.addComments = false 
     998> 
     999>      // Behaviors non sono gestiti per default 
     1000>      // Imposta questo a true per gestirli 
     1001>      propel.builder.AddBehaviors = false 
     1002> 
     1003>Dopo che hai modificato le impostazioni di `propel.ini` non dimenticare di fare il rebuild del modello affinché le modifiche abbiano effetto. 
     1004 
     1005 
     1006Sommario 
     1007-------- 
     1008 
     1009Symfony usa Propel come ORM e Creole come layer di astrazione del database. Significa che prima di generare le classi del modello devi descrivere lo schema relazionale in YAML. Quindi, a runtime, utilizza i metodi degli oggetti e delle classi peer per recuperare informazioni su un record od un insieme di record. Puoi farne l'override ed estendere il modello facilmente con classi personalizzate. Le impostazioni di connessione sono definite nel file `databases.yml`, che può supportarne anche più di una. E la linea di comando contiene comandi speciali per evitare di definire due volte la stessa struttura. 
     1010 
     1011Il livello del modello è il più complesso del framework. Una delle ragioni di tale complessità è dovuta alla natura intrinsecamente intricata dell'argomento. Le questioni relative alla sicurezza sono cruciali per un'applicazione web e non devono essere ignorate. Un'altra ragione è il fatto che symfony si adatta meglio ad applicazioni che scalano su dimensioni medio-grandi a livello enterprise. In tali applicazioni, le automazioni messe a disposizione da symfony rappresentano un vero guadagno di tempo, per cui vale la pena investirne per impararle. 
     1012 
     1013Per cui non esitare ad impiegare un po' di tempo nel testare gli oggetti ed i metodi del modello per capirli pienamente. La solidità e la scalabilità saranno una grande ricompensa. 
     1014 
     1015}}}