Development

Documentation/it_IT/book/forms/04-Propel-Integration (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/it_IT/book/forms/04-Propel-Integration

Show
Ignore:
Author:
garak (IP: 85.18.214.242)
Timestamp:
10/30/08 17:46:52 (9 years ago)
Comment:

da finire...

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/it_IT/book/forms/04-Propel-Integration

    v0 v1  
     1{{{ 
     2#!WikiMarkdown 
     3 
     4Integrazione con Propel 
     5======================= 
     6 
     7In un progetto web, la maggior parte delle form è usata per creare o modificare oggetti del modello. Questi oggetti sono solitamente serializzati in un database grazie ad un ORM. Il sistema di form di symfony offre un livello addizionale per interfacciarsi con Propel, l'ORM predefinito in symfony, rendendo l'implementazione delle form basate su questi oggetti più facile. 
     8 
     9Questo capitolo dettaglia il modo in cui integrare le form con gli oggetti del modello di Propel. È caldamente consigliato di essere già pratici con Propel e la sua integrazione in symfony. Se non fosse così, fare riferimento al capitolo <a href="/wiki/Documentation/it_IT/book/1.1/08-Inside-the-Model-Layer">All'interno del layer Modello</a> nella guida a symfony. 
     10 
     11Prima di cominciare 
     12------------------- 
     13 
     14In questo capitolo creeremo un sistema di gestione di articoli. Inizieremo con lo schema del database, che è composto da cinque tabelle: `article`, `author`, `category`, `tag`, e `article_tag`, come mostrato nel Listato 4-1. 
     15 
     16Listato 4-1 - Schema del database 
     17 
     18    // config/schema.yml 
     19    propel: 
     20      article: 
     21        id:           ~ 
     22        title:        { type: varchar(255), required: true } 
     23        slug:         { type: varchar(255), required: true } 
     24        content:      longvarchar 
     25        status:       varchar(255) 
     26        author_id:    { type: integer, required: true, foreignTable: author, foreignReference: id, OnDelete: cascade } 
     27        category_id:  { type: integer, required: false, foreignTable: category, foreignReference: id, onDelete: setnull } 
     28        published_at: timestamp 
     29        created_at:   ~ 
     30        updated_at:   ~ 
     31        _uniques: 
     32          unique_slug: [slug] 
     33      
     34      author: 
     35        id:           ~ 
     36        first_name:   varchar(20) 
     37        last_name:    varchar(20) 
     38        email:        { type: varchar(255), required: true } 
     39        active:       boolean 
     40      
     41      category: 
     42        id:           ~ 
     43        name:         { type: varchar(255), required: true } 
     44      
     45      tag: 
     46        id:           ~ 
     47        name:         { type: varchar(255), required: true } 
     48      
     49      article_tag: 
     50        article_id:   { type: integer, foreignTable: article, foreignReference: id, primaryKey: true, onDelete: cascade } 
     51        tag_id:       { type: integer, foreignTable: tag, foreignReference: id, primaryKey: true, onDelete: cascade } 
     52  
     53Ecco le relazioni tra le tabelle: 
     54 
     55  * Relazione 1-n tra la tabella `article` e la tabella `author`: un articolo è scritto da uno ed un solo autore 
     56  * Relazione 1-n tra la tabella `article` e la tabella `category`: un articolo appartiene ad una o nessuna categoria 
     57  * Relazione n-n tra le tabelle `article` e `tag` 
     58 
     59Generazione delle classi delle form 
     60----------------------------------- 
     61 
     62Vogliamo modificare le informazioni sulle tabelle `article`, `author`, `category`, e `tag`. Per poterlo fare, abbiamo bisogno di creare delle form legate a ciascuna di queste tabelle e di configurare dei widget e dei validatori correlati allo schema del database. Pur essendo possibile creare tali form a mano, è un processo lungo, noioso e soprattutto che costringe a ripetere lo stesso tipo di informazione in diversi file (nomi di colonne e campi, dimensione massima di colonne e campi, ecc...). Inoltre, ogni volta che cambiamo il modello, dobbiamo cambiare anche le relative classi delle form. Fortunatamente, il plugin Propel ha un task `propel:build-forms` che automatizza il processo di generazione delle form legate al modello degli oggetti: 
     63 
     64    $ ./symfony propel:build-forms 
     65 
     66Durante la generazione delle form, il task crea una classe per tabelle con validatori e widget per ogni colonna, usando l'introspezione del modello e considerando le relazioni tra le tabelle. 
     67 
     68>**NOTE** 
     69>Anche `propel:build-all` e `propel:build-all-load` aggiornano le classi delle form, invocando automaticamente il task `propel:build-forms`. 
     70 
     71Dopo aver eseguito questi task, una struttura di file è stata creata nella cartella `lib/form/`. Ecco i file creati per il nostro schema di esempio: 
     72 
     73    lib/ 
     74      form/ 
     75        BaseFormPropel.class.php 
     76        ArticleForm.class.php 
     77        ArticleTagForm.class.php 
     78        AuthorForm.class.php 
     79        CategoryForm.class.php 
     80        TagForm.class.php 
     81        base/ 
     82          BaseArticleForm.class.php 
     83          BaseArticleTagForm.class.php 
     84          BaseAuthorForm.class.php 
     85          BaseCategoryForm.class.php 
     86          BaseTagForm.class.php 
     87 
     88Il task `propel:build-forms` genera due classi per ogni tabella dello schema, una classe base nella cartella `lib/form/base` ed una nella cartella `lib/form/`. Ad esempio la tabella `author` ha le classi generate `BaseAuthorForm` e `AuthorForm` nei file `lib/form/base/BaseAuthorForm.class.php` e `lib/form/AuthorForm.class.php`. 
     89 
     90>**SIDEBAR** 
     91>Cartella di generazione delle form 
     92> 
     93>Il task `propel:build-forms` genera questi file in una struttura simile a quella di Propel. L'attributo del package dello schema di Propel consente di mettere insieme logicamente dei sottoinsiemi di tabelle. Il package predefinito è `lib.model`, quindi Propel genera questi file nella cartella `lib/model/` e le classi delle form nella cartella `lib/form`. Usando il package `lib.model.cms`, come mostrato nell'esempio qui sotto, le classi Propel saranno generate nella cartella `lib/model/cms/` e le classi della form nella cartella `lib/form/cms/`. 
     94> 
     95>       propel: 
     96>         _attributes: { noXsd: false, defaultIdMethod: none, package: lib.model.cms } 
     97>         # ... 
     98> 
     99>I package sono utili per suddividere lo schema del database e rilasciare form con un plugin, come vedremo nel Capitolo 5. 
     100> 
     101>Per ulteriori informazioni sui package Propel, fai riferimento al capitolo <a href="/wiki/Documentation/it_IT/book/1.1/08-Inside-the-Model-Layer">All'interno del layer Modello</a> nella guida a symfony 
     102 
     103La tabella seguente riassume la gerarchia tra le varie classi coinvolte nella definizione della form `AuthorForm`. 
     104 
     105<table> 
     106  <tr> 
     107    <th>Classe</th> 
     108    <th>Package</th> 
     109    <th>Per</th> 
     110    <th>Descrizione</th> 
     111  </tr> 
     112  <tr> 
     113    <td>AuthorForm</td> 
     114    <td>progetto</td> 
     115    <td>sviluppatore</td> 
     116    <td>Sovrascrive la form generata</td> 
     117  </tr> 
     118  <tr> 
     119    <td>BaseAuthorForm</td> 
     120    <td>progetto</td> 
     121    <td>symfony</td> 
     122    <td>Basata sullo schema e sovrascritta ad ogni esecuzione di <code>propel:build-forms</code></td> 
     123  </tr> 
     124  <tr> 
     125    <td>BaseFormPropel</td> 
     126    <td>progetto</td> 
     127    <td>sviluppatore</td> 
     128    <td>Consente la personalizzazione globale delle form Propel</td> 
     129  </tr> 
     130  <tr> 
     131    <td>sfFormPropel</td> 
     132    <td>plugin Propel</td> 
     133    <td>symfony</td> 
     134    <td>Base delle form Propel</td> 
     135  </tr> 
     136  <tr> 
     137    <td>sfForm</td> 
     138    <td>symfony</td> 
     139    <td>symfony</td> 
     140    <td>Base delle form symfony</td> 
     141  </tr> 
     142</table> 
     143 
     144Per creare o modificare un oggetto della classe `Author`, useremo la classe `AuthorForm`, descritta nel Listato 4-2. Come puoi notare, questa classe non contiene metodi perché eredita da `BaseAuthorForm`, che viene generata tramite la configurazione. La classe `AuthorForm` è la classe che useremo per personalizzare e sovrascrivere la configurazione. 
     145 
     146Listato 4-2 - Classe `AuthorForm` 
     147 
     148    [php] 
     149    class AuthorForm extends BaseAuthorForm 
     150    { 
     151      public function configure() 
     152      { 
     153      } 
     154    } 
     155 
     156Il Listato 4-3 mostra la classe `BaseAuthorForm`, con i validatori ed i widget generati tramite l'introspezione del modello per la tabella `author`. 
     157 
     158Listato 4-3 - Classe `BaseAuthorForm`, che rappresenta la form della tabella `author` 
     159 
     160    [php] 
     161    class BaseAuthorForm extends BaseFormPropel 
     162    { 
     163      public function setup() 
     164      { 
     165        $this->setWidgets(array( 
     166          'id'         => new sfWidgetFormInputHidden(), 
     167          'first_name' => new sfWidgetFormInput(), 
     168          'last_name'  => new sfWidgetFormInput(), 
     169          'email'      => new sfWidgetFormInput(), 
     170        )); 
     171      
     172        $this->setValidators(array( 
     173          'id'         => new sfValidatorPropelChoice(array('model' => 'Author', 'column' => 'id', 'required' => false)), 
     174          'first_name' => new sfValidatorString(array('max_length' => 20, 'required' => false)), 
     175          'last_name'  => new sfValidatorString(array('max_length' => 20, 'required' => false)), 
     176          'email'      => new sfValidatorString(array('max_length' => 255)), 
     177        )); 
     178      
     179        $this->widgetSchema->setNameFormat('author[%s]'); 
     180      
     181        $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema); 
     182      
     183        parent::setup(); 
     184      } 
     185      
     186      public function getModelName() 
     187      { 
     188        return 'Author'; 
     189      } 
     190    } 
     191 
     192La classe generata assomiglia molto alle form che abbiamo già creato nel capitolo precedente, tranne per alcuni aspetti: 
     193 
     194  * La classe base è `BaseFormPropel` invece di `sfForm` 
     195  * Le configurazioni del validatore e del widget stanno nel metodo `setup()` invece che nel metodo `configure()` 
     196  * Il metodo `getModelName()` restituisce la classe Propel correlata a questa form 
     197 
     198>**SIDEBAR** 
     199>Personalizzazione globale delle form Propel 
     200> 
     201>Oltre alle classi generate per ogni tabella, `propel:build-forms` genera anche la classe `BaseFormPropel`. Questa classe vuota è la classe base della cartella `lib/form/base/` e consente di configurare il comportamento di ogni form Propel globalmente. Per esempio, è possibile cambiare facilmente il formattatore predefinito di tutte le form Propel: 
     202> 
     203>     abstract class BaseFormPropel extends sfFormPropel 
     204>     { 
     205>       public function setup() 
     206>       { 
     207>         sfWidgetFormSchema::setDefaultFormFormatterName('div'); 
     208>       } 
     209>     } 
     210>Avrai notato che la classe `BaseFormPropel` eredita dalla classe `sfFormPropel`. Questa classe incorpora delle funzionalità specifiche di Propel e tra le altre cose gestisce la serializzazione degli oggetti nel database dai valori inviati nella form. 
     211> 
     212> <b>TIP</b> Le classi base usano il metodo `setup()` per la configurazione al posto del metodo `configure()`. Questo consente allo sviluppatore di sovrascrivere la configurazione delle classi generate vuote senza gestire la chiamata a `parent::configure()`. 
     213 
     214I nomei dei campi della form sono identici ai nomi delle colonne dello schema: `id`, `first_name`, `last_name`, `email`. 
     215 
     216Per ogni colonna della tabelle `author`, il task `propel:build-forms` genera un widget ed un validatore secondo la definizione dello schema. Il task genera sempre i validatori più sicuri possibile. Consideriamo il campo `id`. Potremmo solo verificare se il valore è un intero valido. Invece il validatore generato ci consente anche di validare che l'identificatore esista veramente (per modificare un oggetto esistente) o che sia vuoto (per poter creare un nuovo oggetto). Questa è una validazione più forte. 
     217 
     218Le form generate possono essere usate immediatamente. Aggiungi un'istruzione `<?php echo $form ?>` e questo consentirà di creare delle form funzionali con validazione <b>senza scrivere una sola riga di codice</b>. 
     219 
     220Oltre alla possibilità di crare rapidamente dei prototipi, le form generate sono facilmente estensibili senza dover modificare le classi generate. Questo grazie al meccanismo di ereditarietà delle classi base e delle classi form. 
     221 
     222Alla fine di ogni evoluzione dello schema del database, il task consente di genereare nuovamente le form per considerare le modifiche allo schema, senza sovrascrivere le personalizzazioni che potresti aver fatto. 
     223 
     224Il generatore CRUD 
     225------------------ 
     226 
     227Ora che abbiamo le classi generate delle form, vediamo quanto è facile creare un modulo symfony per gestire gli oggetti da un browser. Vogliamo creare, modificare, cancellare gli oggetti delle classi `Article`, `Author`, `Category`, `Tag`. Iniziamo con la creazione del modulo per la classe `Author`. Anche se potremmo creare manualmente un modulo, il plugin Propel fornisce il task `propel:generate-crud`, che genera un modulo CRUD basato sulle classi dei modelli degli oggetti Propel. Usando la form che abbiamo generato nella sezione precedente: 
     228 
     229    $ ./symfony propel:generate-crud frontend author Author 
     230 
     231`propel:generate-crud` accetta tre parametri: 
     232 
     233  * `frontend` : nome dell'applicazione in cui vuoi creare il modulo 
     234  * `author` : nome del modulo che vuoi creare 
     235  * `Author` : nome della classe del modello per cui vuoi creare il modulo 
     236 
     237>**NOTE** 
     238>CRUD sta per Creation / Retrieval / Update / Deletion (Creazione / Recupero / Aggiornamento / Cancellazione) e riassume le quattro operazioni basilari che possiamo usare con i dati dei modelli. 
     239 
     240Nel Listato 4-4 vediamo che il task ha generato cinque azioni, che ci consentono di elencare (`index`), creare (`create`), modificare (`edit`), salvare (`update`), cancellare (`delete`) gli oggetti della classe `Author`. 
     241 
     242Listato 4-4 - La classe `authorActions` generata dal task 
     243 
     244    [php] 
     245    // apps/frontend/modules/author/actions/actions.class.php 
     246    class authorActions extends sfActions 
     247    { 
     248      public function executeIndex() 
     249      { 
     250        $this->authorList = AuthorPeer::doSelect(new Criteria()); 
     251      } 
     252      
     253      public function executeCreate() 
     254      { 
     255        $this->form = new AuthorForm(); 
     256      
     257        $this->setTemplate('edit'); 
     258      } 
     259      
     260      public function executeEdit($request) 
     261      { 
     262        $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id'))); 
     263      } 
     264      
     265      public function executeUpdate($request) 
     266      { 
     267        $this->forward404Unless($request->isMethod('post')); 
     268      
     269        $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id'))); 
     270      
     271        $this->form->bind($request->getParameter('author')); 
     272        if ($this->form->isValid()) 
     273        { 
     274          $author = $this->form->save(); 
     275      
     276          $this->redirect('author/edit?id='.$author->getId()); 
     277        } 
     278      
     279        $this->setTemplate('edit'); 
     280      } 
     281      
     282      public function executeDelete($request) 
     283      { 
     284        $this->forward404Unless($author = AuthorPeer::retrieveByPk($request->getParameter('id'))); 
     285      
     286        $author->delete(); 
     287      
     288        $this->redirect('author/index'); 
     289      } 
     290    } 
     291 
     292In questo modulo, il ciclo di vita della form è gestito da tre metodi: `create`, `edit`, `update`. È anche possibile chiedere al task `propel:generate-crud` di generare solo un metodo che copre le funzionalità di questi tre metodi, con l'opzione `--non-atomic-actions`: 
     293 
     294    $ ./symfony propel:generate-crud frontend author Author --non-atomic-actions 
     295 
     296Il codice generato usando `--non-atomic-actions` (Listato 4-5) è più conciso e meno prolisso. 
     297 
     298Listato 4-5 - La classe `authorActions` generata con l'opzione `--non-atomic-actions` 
     299 
     300    [php] 
     301    class authorActions extends sfActions 
     302    { 
     303      public function executeIndex() 
     304      { 
     305        $this->authorList = AuthorPeer::doSelect(new Criteria()); 
     306      } 
     307      
     308      public function executeEdit($request) 
     309      { 
     310        $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id'))); 
     311      
     312        if ($request->isMethod('post')) 
     313        { 
     314          $this->form->bind($request->getParameter('author')); 
     315          if ($this->form->isValid()) 
     316          { 
     317            $author = $this->form->save(); 
     318      
     319            $this->redirect('author/edit?id='.$author->getId()); 
     320          } 
     321        } 
     322      } 
     323      
     324      public function executeDelete($request) 
     325      { 
     326        $this->forward404Unless($author = AuthorPeer::retrieveByPk($request->getParameter('id'))); 
     327      
     328        $author->delete(); 
     329      
     330        $this->redirect('author/index'); 
     331      } 
     332    } 
     333 
     334Il task genera anche due template, `indexSuccess` ed `editSuccess`. Il template `editSuccess` è stato generato senza usare l'istruzione `<?php echo $form ?>`. Possiamo modificare questo comportamento, usando `--non-verbose-templates`: 
     335 
     336    $ ./symfony propel:generate-crud frontend author Author --non-verbose-templates 
     337 
     338Questa opzione è utile durante la fase di prototipizzazione, come mostra il Listato 4-6. 
     339 
     340Listato 4-6 - Il template `editSuccess` 
     341 
     342    // apps/frontend/modules/author/templates/editSuccess.class.php 
     343    <?php $author = $form->getObject() ?> 
     344    <h1><?php echo $author->isNew() ? 'New' : 'Edit' ?> Author</h1> 
     345      
     346    <form action="<?php echo url_for('author/edit'.(!$author->isNew() ? '?id='.$author->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>> 
     347      <table> 
     348        <tfoot> 
     349          <tr> 
     350            <td colspan="2"> 
     351              &nbsp;<a href="<?php echo url_for('author/index') ?>">Cancel</a> 
     352              <?php if (!$author->isNew()): ?> 
     353                &nbsp;<?php echo link_to('Delete', 'author/delete?id='.$author->getId(), array('post' => true, 'confirm' => 'Are you sure?')) ?> 
     354              <?php endif; ?> 
     355              <input type="submit" value="Save" /> 
     356            </td> 
     357          </tr> 
     358        </tfoot> 
     359        <tbody> 
     360          <?php echo $form ?> 
     361        </tbody> 
     362      </table> 
     363    </form> 
     364 
     365>**TIP** 
     366>L'opzione `--with-show` ci consente di generare un'azione ed un template che possiamo usare per vedere un oggetto (in sola lettura). 
     367 
     368Ora puoi aprire in un browser la URL `/frontend_dev.php/author` per vedere il modulo generato (Figura 4-1 e Figura 4-2). Prenditi un po' di tempo per giocare con l'interfaccia. Grazie al modulo generato puoi elencare gli autori, aggiungerne uno nuovo, modificare e anche cancellare. Noterai anche che le regole di validazione stanno funzionando. 
     369 
     370Figura 4-1 - Lista degli autori 
     371 
     372![Lista degli autori](http://www.symfony-project.org/images/forms_book/en/04_01.png "Lista degli autori") 
     373 
     374Figura 4-2 - Modifica di un autore con errori di validazione 
     375 
     376![Modifica di un autore con errori di validazione](http://www.symfony-project.org/images/forms_book/en/04_02.png "Modifica di un autore con errori di validazione") 
     377 
     378Possiamo ora ripetere l'operazione con la classe `Article`: 
     379 
     380    $ ./symfony propel:generate-crud frontend article Article --non-verbose-templates --non-atomic-actions 
     381 
     382Il codice generato è molto simile al codice della classe `Author`. Tuttavia, se provi a creare un nuovo articolo, il codice lancia un errore fatale, come puoi vedere in Figura 4-3. 
     383 
     384Figura 4-3 - Le tabelle collegate devono definire il metodo `__toString()` 
     385 
     386![Le tabelle collegate devono definire il metodo `__toString()`](http://www.symfony-project.org/images/forms_book/en/04_03.png "Le tabelle collegate devono definire il metodo `__toString()`") 
     387 
     388La form `ArticleForm` usa il widget `sfWidgetFormPropelSelect` per rappresentare la relazione tra l'oggetto `Article` e l'oggetto `Author`. Questo widget crea un menù a tendina con gli autori. Durante la visualizzazione, gli oggetti autori sono convertiti in stringhe di caratteri usando il metodo magico `__toString()`, che deve essere definito nella classe `Author`, come mostrato nel Listato 4-7. 
     389 
     390Listato 4-7 - Implementare il metodo `__toString()` per la classe `Author` 
     391 
     392    [php] 
     393    class Author extends BaseAuthor 
     394    { 
     395      public function __toString() 
     396      { 
     397        return $this->getFirstName().' '.$this->getLastName(); 
     398      } 
     399    } 
     400 
     401Proprio come la classe `Author`, puoi creare dei metodi `__toString()` per le altre classi del nostro modello: `Article`, `Category`, `Tag`. 
     402 
     403>**TIP** 
     404>L'opzione `method` del widget `sfWidgetFormPropelSelect` cambia il metodo usato per rappresentare un oggetto in formato testuale. 
     405 
     406La Figura 4-4 mostra come creare un articolo dopo aver implementato il metodo `__toString()`. 
     407 
     408Figura 4-4 - Creare un articolo 
     409 
     410![Creare un articolo](http://www.symfony-project.org/images/forms_book/en/04_04.png "Creare un articolo") 
     411 
     412(TODO...)