Development

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

You must first sign up to be able to contribute.

Changes between Version 1 and Version 2 of Documentation/it_IT/book/forms/04-Propel-Integration

Show
Ignore:
Author:
garak (IP: 85.18.214.242)
Timestamp:
10/31/08 12:38:38 (8 years ago)
Comment:

--

Legend:

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

    v1 v2  
    410410![Creare un articolo](http://www.symfony-project.org/images/forms_book/en/04_04.png "Creare un articolo") 
    411411 
    412 (TODO...) 
     412Personalizzare le form generate 
     413------------------------------- 
     414 
     415I task `propel:build-forms` e `propel:generate-crud` ci consentono di creare moduli funzionali in symfony per elencare, creare, modificare e cancellare gli oggetti del modello. Questi moduli tengono in considerazione non solo le regole di validazione del modello, ma anche le relazioni tra le tabelle. Tutto ciò avviene senza scrivere una sola riga di codice! 
     416 
     417È arrivato il momento di personalizzare il codice generato. Se le form delle classi considerano già diversi elementi, alcuni aspetti avranno bisogno di una personalizzazione. 
     418 
     419### Personalizzare i validatori ed i widget 
     420 
     421Iniziamo col configurare i validatori ed i widget generati di default. 
     422 
     423La form `ArticleForm` ha un campo `slug`. Lo slug è una stringa di caratteri che rappresenta univocamente l'articolo nella URL. Per esempio, lo slug di un articolo il cui titolo è "Ottimizzare lo sviluppo con symfony" è `12-ottimizzare-lo-sviluppo-con-symfony`, in cui `12` è l'`id` dell'articolo. Questo campo solitamente viene calcolato in modo automatico quando l'oggetto viene salvato e dipende del titolo, ma ha la possibilità di essere sovrascritto esplicitamente dall'utente. Anche se questo campo è obbligatorio nello schema, non può esserlo nella form. Per questo motivo modifichiamo il validatore e lo rendiamo opzionale, come nel Listato 4-8. Personalizzeremo anche il campo `content`, aumentando la sua dimensione e forzando l'utente ad inserirvi almeno cinque caratteri. 
     424 
     425Listato 4-8 - Personalizzare i validatori ed i widget 
     426 
     427    [php] 
     428    class ArticleForm extends BaseArticleForm 
     429    { 
     430      public function configure() 
     431      { 
     432        // ... 
     433      
     434        $this->validatorSchema['slug']->setOption('required', false); 
     435        $this->validatorSchema['content']->setOption('min_length', 5); 
     436      
     437        $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40)); 
     438      } 
     439    } 
     440 
     441Abbiamo usato gli oggetti `validatorSchema` e `widgetSchema` come array PHP. Queste array prendono il nome del campo come chiave e restituiscono rispettivamente l'oggetto validatore ed il relativo oggetto widget. 
     442 
     443>**NOTE** 
     444>Per poter usare gli oggetti come array PHP, le classi `sfValidatorSchema` e `sfWidgetFormSchema` implementano l'interfaccia `ArrayAccess`, disponibile dalla versione 5 di PHP. 
     445 
     446Per assicurarci che due articoli non abbiano lo stesso slug, una costante di univocità è stata aggiunta nella definizione dello schema. Questa costante a livello di database ha una corrispondenza nella form `ArticleForm` tramite il validatore `sfValidatorPropelUnique`. Questo validatore può verificare l'univocità di ogni campo della form. È utile tra l'altro per verificare ad esempio l'univocità di un indirizzo email usato come login. Il Listato 4-9 mostra come usarlo nella form `ArticleForm`. 
     447 
     448Listato 4-9 - Usare il validatore `sfValidatorPropelUnique` per verificare l'univocità di un campo 
     449 
     450    [php] 
     451    class BaseArticleForm extends BaseFormPropel 
     452    { 
     453      public function setup() 
     454      { 
     455        // ... 
     456      
     457        $this->validatorSchema->setPostValidator( 
     458          new sfValidatorPropelUnique(array('model' => 'Article', 'column' => array('slug'))) 
     459        ); 
     460      } 
     461    } 
     462 
     463Il validatore `sfValidatorPropelUnique` è un `postValidator` che gira su tutti i dati dopo la validazione individuale di ciascun campo. Per poter validare l'univocità dello slug, il validatore deve poter accedere non solo al valore di `slug`, ma anche al valore della chiave primaria (o delle chiavi primarie). Le regole di validazione sono quindi diverse tra creazione e modifica, poiché `slug` può restare lo stesso durante l'aggiornamento di un articolo. 
     464 
     465Ora personalizziamo il campo `active` della tabella `authoer`, usato per sapere se l'utente è attivo. Il Listato 4-10 mostra come escludere gli autori inattivi dalla form `ArticleForm`, modificando l'opzione `criteria` del widget `sfWidgetPropelSelect` connesso al campo `author_id`. L'opzione `criteria` accetta un oggetto Criteria di Propel, consentendo di accorciare la lista delle opzioni disponibili. 
     466 
     467Listato 4-10 - Personalizzare il widget `sfWidgetPropelSelect` 
     468 
     469    [php] 
     470    class ArticleForm extends BaseArticleForm 
     471    { 
     472      public function configure() 
     473      { 
     474        // ... 
     475      
     476        $authorCriteria = new Criteria(); 
     477        $authorCriteria->add(AuthorPeer::ACTIVE, true); 
     478      
     479        $this->widgetSchema['author_id']->setOption('criteria', $authorCriteria); 
     480      } 
     481    } 
     482 
     483Anche se la personalizzazione dei widget può farci accorciare la lista delle opzioni disponibili, non dobbiamo dimenticare di considerare questa abbreviazione a livello di validatore, come mostrato nel Listato 4-11. Come il widget `sfWidgetProperSelect`, il validatore `sfValidatorPropelChoice` accetta un'opzione `criteria` per accorciare le opzioni valide per un campo. 
     484 
     485Listato 4-11 - Personalizzare il validatore `sfValidatorPropelChoice` 
     486 
     487    [php] 
     488    class ArticleForm extends BaseArticleForm 
     489    { 
     490      public function configure() 
     491      { 
     492        // ... 
     493      
     494        $authorCriteria = new Criteria(); 
     495        $authorCriteria->add(AuthorPeer::ACTIVE, true); 
     496      
     497        $this->widgetSchema['author_id']->setOption('criteria', $authorCriteria); 
     498        $this->validatorSchema['author_id']->setOption('criteria', $authorCriteria); 
     499      } 
     500    } 
     501 
     502Nell'esempio precedente abbiamo definito l'oggetto `Criteria` direttamente nel metodo `configure()`. Nel nostro progetto, questi criteri saranno certamente utili in altre circostanze, quindi è meglio creare un metodo `getActiveAuthorsCriteria()` nella classe `AuthorPeer` e richiamarlo da `ArticleForm`, come mostrato nel Listato 4-12. 
     503 
     504Listato 4-12 - Rifattorizzare i `criteria` nel modello 
     505 
     506    [php] 
     507    class AuthorPeer extends BaseAuthorPeer 
     508    { 
     509      static public function getActiveAuthorsCriteria() 
     510      { 
     511        $criteria = new Criteria(); 
     512        $criteria->add(AuthorPeer::ACTIVE, true); 
     513      
     514        return $criteria; 
     515      } 
     516    } 
     517      
     518    class ArticleForm extends BaseArticleForm 
     519    { 
     520      public function configure() 
     521      { 
     522        $authorCriteria = AuthorPeer::getActiveAuthorsCriteria(); 
     523        $this->widgetSchema['author_id']->setOption('criteria', $authorCriteria); 
     524        $this->validatorSchema['author_id']->setOption('criteria', $authorCriteria); 
     525      } 
     526    } 
     527 
     528>**TIP** 
     529>Come il widget `sfWidgetPropelSelect` ed il validatore `sfValidatorPropelChoice` rappresentano una relazione 1-n tra duet abelle, i validatori `sfWidgetPropelSelectMany` e `sfValidatorPropelChoiceMany` rappresentano una relazione n-n ed accettano le medesime opzioni. nella form `ArticleForm`, queste classi sono utilizzate per rappresentare una relazione tra la tabella `article` e la tabella `tag`. 
     530 
     531### Cambiare validatore 
     532 
     533Essendo `email` definita come `varchar(255)` nello schema, symfony ha creato un validatore `sfValidatorString()` che limita la lunghezza massima a 255 caratteri. Inoltre si suppone di avere in questo campo un'email valida. Il Listato 4-14 sostituisce il validatore generato con un validatore `sfValidatorEmail`. 
     534 
     535Listato 4-13 - Cambiare il validatore del campo `email` della classe `AuthorForm` 
     536 
     537    [php] 
     538    class AuthorForm extends BaseAuthorForm 
     539    { 
     540      public function configure() 
     541      { 
     542        $this->validatorSchema['email'] = new sfValidatorEmail(); 
     543      } 
     544    } 
     545 
     546### Aggiungere un validatore 
     547 
     548Abbiamo osservato nel capitolo precedente come modificare i validatori generati. Ma nel caso del campo `email`, sarebbe utile mantenere la validazione della lunghezza massima. Nel Listato 4-14, usiamo il validatore `sfValidatorAnd` per garantire la validità dell'email e verificare la lunghezza massima consentita per il campo. 
     549 
     550Listato 4-14 - Uso di un validatore multiplo 
     551 
     552    [php] 
     553    class AuthorForm extends BaseAuthorForm 
     554    { 
     555      public function configure() 
     556      { 
     557        $this->validatorSchema['email'] = new sfValidatorAnd(array( 
     558          new sfValidatorString(array('max_length' => 255)), 
     559          new sfValidatorEmail(), 
     560        )); 
     561      } 
     562    } 
     563 
     564L'esempio precedente non è perfetto, poiché se decidiamo più avanti di modificare la lunghezza del campo `email` nello schema del database, dovremo preoccuparci di farlo anche nella form. Invece di sostituire il validatore generato, è meglio aggiungerne uno, come mostrato nel Listato 4-15. 
     565 
     566Listato 4-15 - Aggiunta di un validatore 
     567 
     568    [php] 
     569    class AuthorForm extends BaseAuthorForm 
     570    { 
     571      public function configure() 
     572      { 
     573        $this->validatorSchema['email'] = new sfValidatorAnd(array( 
     574          $this->validatorSchema['email'], 
     575          new sfValidatorEmail(), 
     576        )); 
     577      } 
     578    } 
     579 
     580### Cambiare widget 
     581 
     582Nello schema del database, il campo `status` della tabella `article` memorizza lo status dell'articolo come una stringa di caratteri. I valori possibili stono stati definiti nella classe `ArticePeer`, come mostrato nel Listato 4-16. 
     583 
     584Listato 4-16 - Definizione degli status disponibili nella classe `ArticlePeer` 
     585 
     586    [php] 
     587    class ArticlePeer extends BaseArticlePeer 
     588    { 
     589      static protected $statuses = array('draft', 'online', 'offline'); 
     590      
     591      static public function getStatuses() 
     592      { 
     593        return self::$statuses; 
     594      } 
     595      
     596      // ... 
     597    } 
     598 
     599Quando si modifica un articolo, il campo `status` deve essere rappresentato da un menù a tendina invece che da un campo di testo. Per fare questo, cambiamo il widget che abbiamo usato, come mostrato nel Listato 4-17. 
     600 
     601Listato 4-17 - Modifica del widget del campo `status` 
     602 
     603    [php] 
     604    class ArticleForm extends BaseArticleForm 
     605    { 
     606      public function configure() 
     607      { 
     608        $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); 
     609      } 
     610    } 
     611 
     612Per scrupolo, cambiamo anche il validatore, per assicurarci che lo status scelto appartenga veramente alla lista delle possibili opzioni (Listato 4-18). 
     613 
     614Listato 4-18 - Modifica del validatore del campo `status` 
     615 
     616    [php] 
     617    class ArticleForm extends BaseArticleForm 
     618    { 
     619      public function configure() 
     620      { 
     621        $statuses = ArticlePeer::getStatuses(); 
     622      
     623        $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => $statuses)); 
     624      
     625        $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys($statuses))); 
     626      } 
     627    } 
     628 
     629### Cancellare un campo 
     630 
     631La tabella `article` ha due colonne speciali, `created_at` e `updated_at`, il cui aggiornamento è gestito automaticamente da Propel. Quindi dobbiamo cancellarle dalla form, come mostra il Listato 4-19, per impedire all'utente di modificarli. 
     632 
     633Listato 4-19 - Cancellare un campo 
     634 
     635    [php] 
     636    class ArticleForm extends BaseArticleForm 
     637    { 
     638      public function configure() 
     639      { 
     640        unset($this->validatorSchema['created_at']); 
     641        unset($this->widgetSchema['created_at']); 
     642      
     643        unset($this->validatorSchema['updated_at']); 
     644        unset($this->widgetSchema['updated_at']); 
     645      } 
     646    } 
     647 
     648Per poter cancellare un campo, è necessario cancellare il suo validatore ed il suo widget. Il Listato 4-20 mostra come sia possibile cancellarli entrambi in un solo colpo, usando la form come un'array PHP. 
     649 
     650Listato 4-20 - Cancellare un campo usando la form come array PHP 
     651 
     652    [php] 
     653    class ArticleForm extends BaseArticleForm 
     654    { 
     655      public function configure() 
     656      { 
     657        unset($this['created_at'], $this['updated_at']); 
     658      } 
     659    } 
     660 
     661### Riassunto 
     662 
     663Per riassumere, il Listato 4-21 ed il Listato 4-22 mostrano le form `ArticleForm` e `AuthorForm` dopo la nostra personalizzazione. 
     664 
     665Listato 4-21 - form `ArticleForm` 
     666 
     667    [php] 
     668    class ArticleForm extends BaseArticleForm 
     669    { 
     670      public function configure() 
     671      { 
     672        $authorCriteria = AuthorPeer::getActiveAuthorsCriteria(); 
     673      
     674        // widgets 
     675        $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40)); 
     676        $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); 
     677        $this->widgetSchema['author_id']->setOption('criteria', $authorCriteria); 
     678      
     679        // validators 
     680        $this->validatorSchema['slug']->setOption('required', false); 
     681        $this->validatorSchema['content']->setOption('min_length', 5); 
     682        $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticlePeer::getStatuses()))); 
     683        $this->validatorSchema['author_id']->setOption('criteria', $authorCriteria); 
     684      
     685        unset($this['created_at']); 
     686        unset($this['updated_at']); 
     687      } 
     688    } 
     689 
     690Listato 4-22 - form `AuthorForm` 
     691 
     692    [php] 
     693    class AuthorForm extends BaseAuthorForm 
     694    { 
     695      public function configure() 
     696      { 
     697        $this->validatorSchema['email'] = new sfValidatorAnd(array( 
     698          $this->validatorSchema['email'], 
     699          new sfValidatorEmail(), 
     700        )); 
     701      } 
     702    } 
     703 
     704L'uso di `propel:build-forms` consente di generare automaticamente la maggior parte dgli elementi, tramite l'introspezione del modello. Questa automatizzazione è utile per diverse ragioni: 
     705 
     706  * Rende più facile la vita dello sviluppatore, risparmiandogli del lavoro ripetitivo e ridondante. Egli può quindi focalizzarsi sulla personalizzazione dei validatori e dei widget, secondo le specifiche business rule del progetto. 
     707   
     708  * Inoltre, quando lo schema del database viene aggiornato, le form generate sono aggiornate automaticamente. Lo sviluppatore deve solo raffinare la personalizzazione che ha già eseguito. 
     709 
     710La prossima sezione descriverà la personalizzazione delle azioni e dei template generati dal task `propel:generate-crud`. 
     711 
     712Serializzazione delle form 
     713-------------------------- 
     714 
     715La sezione precedente ci ha mostrato come personalizzare le form generate dal task `propel:build-forms`. In questa sezione personalizzeremo il ciclo di vita delle form, iniziando dal codice generato dal task `propel:generate-crud`. 
     716 
     717### Valori di default 
     718 
     719<b>Un'istanza Propel è sempre connessa ad un oggetto Propel</b>. L'oggetto Propel collegato appartiene sempre alla classe restituita dal metodo `getModelName()`. Ad esempio, la form `AuthorForm` può essere collegata solo ad oggetti che appartengono alla classe `Author`. Questo oggetto è o un oggetto vuoto (un'istanza vuota della classe `Author`), oppure l'oggetto inviato al costruttore come primo parametro. Dal momento che il costruttore di una form "media" accetta un'array di valori come primo parametro, il costruttore di una form Propel accetta un oggetto Propel. Tale oggetto viene usato per definire il valore di default di ogni campo della form. Il metodo `getObject()` restituisce l'oggetto correlato all'istanza corrente ed il metodo `isNew()` consente di sapere se l'oggetto è stato inviato tramite il costruttore: 
     720 
     721    [php] 
     722    // creazione di un nuovo oggetto 
     723    $authorForm = new AuthorForm(); 
     724      
     725    print $authorForm->getObject()->getId(); // visualizza null 
     726    print $authorForm->isNew();              // visualizza true 
     727      
     728    // modifica di un oggetto esistente 
     729    $author = AuthorPeer::retrieveByPk(1); 
     730    $authorForm = new AuthorForm($author); 
     731      
     732    print $authorForm->getObject()->getId(); // visualizza 1 
     733    print $authorForm->isNew();              // visualizza false 
     734  
     735### Gestire il ciclo di vita 
     736 
     737Come abbiamo osservato all'inizio del capitolo, l'azione `edit`, mostrata nel Listato 4-23, gestisce il ciclo di vita della form. 
     738 
     739Listato 4-23 - Il metodo `executeEdit` del modulo `author` 
     740 
     741    [php] 
     742    // apps/frontend/modules/author/actions/actions.class.php 
     743    class authorActions extends sfActions 
     744    { 
     745      // ... 
     746      
     747      public function executeEdit($request) 
     748      { 
     749        $author = AuthorPeer::retrieveByPk($request->getParameter('id')); 
     750        $this->form = new AuthorForm($author); 
     751      
     752        if ($request->isMethod('post')) 
     753        { 
     754          $this->form->bind($request->getParameter('author')); 
     755          if ($this->form->isValid()) 
     756          { 
     757            $author = $this->form->save(); 
     758      
     759            $this->redirect('author/edit?id='.$author->getId()); 
     760          } 
     761        } 
     762      } 
     763    } 
     764 
     765Anche se l'azione `edit` assomiglia alle azioni che possiamo aver descritto nei capitoli precedenti, possiamo puntualizzare alcune differenze: 
     766 
     767  * Un oggetto Propel dalla classe `Author` è inviato come primo paramentro al costruttore della form: 
     768 
     769        [php] 
     770        $author = AuthorPeer::retrieveByPk($request->getParameter('id')); 
     771        $this->form = new AuthorForm($author); 
     772   
     773  * Il formato dell'attributo `name` dei widget è personalizzato automaticamente per consentire il recupero dei dati inviati in un'array PHP che prende il nome dalla tabella relativa (`author`): 
     774 
     775        [php] 
     776        $this->form->bind($request->getParameter('author')); 
     777 
     778  * Quando la form è valida, una semplice chiamata al metodo `save()` crea o aggiorna l'oggetto Propel legato alla form: 
     779 
     780        [php] 
     781        $author = $this->form->save(); 
     782     
     783### Creare e modificare un oggetto Propel 
     784 
     785Il codice del Listato 4-23 gestisce con un solo metodo la creazione e la modifica di oggetti della classe `Author`: 
     786 
     787  * Creazione di un nuovo oggetto `Author`: 
     788    * L'azione index viene richiamata senza parametro `id` (`$request->getParameter('id')` è `null`) 
     789    * La chiamata a `retrieveByPk()` di conseguenza restituisce `null` 
     790    * L'oggetto `form` quindi è collegato ad un oggetto Propel `Author` vuoto 
     791    * La chiamata a `$this->form->save()` crea di conseguenza un nuovo oggetto `Author` quando una form valida viene inviata 
     792  * Modifica di un oggetto `Author` esistente: 
     793    * L'azione index viene richiamata con un parametro `id` (`$request->getParameter('id')` è la chiave primaria dell'oggetto `Author` da modificare) 
     794    * La chiamata a `retrieveByPk()` restituisce l'oggetto `Author` relativo alla chiave primaria 
     795    * L'oggetto `form` quindi è collegato all'oggetto appena trovato 
     796    * La chiamata a `$this->form->save()` aggiorna l'oggetto `Author` quando una form valida viene inviata   
     797 
     798### Il metodo `save()` 
     799 
     800Quando una form Propel è valida, il metodo `save()` aggiorna l'oggetto correlato e lo memorizza nel database. Questo metodo in realtà memorizza non solo l'oggetto principale, ma anche gli eventuali oggetti correlati. Ad esempio, la form `ArticleForm` aggiorna i tag connessi ad un articolo. Essendo la relazione tra le tabelle `article` e `tag` n-n, i tag relativi all'articolo sono salvati nella tabella `article_tag` (usando il metodo generato `saveArticleTagList()`). 
     801 
     802Per assicurare una serializzazione coerente, il metodo `save()` inserisce ogni aggiornamento in una transazione. 
     803 
     804>**NOTE** 
     805>Vedremo nel Capitolo 9 che il metodo `save()` aggiorna automaticamente anche le tabella internazionalizzate. 
     806 
     807>**SIDEBAR** 
     808>Usare il metodo `bindAndSave()` 
     809
     810>Il metodo `bindAndSave()` collega i dati che l'utente ha inviato nella form, valida tale form ed aggiorna gli oggetti correlati nel database, tutto in una sola operazione: 
     811
     812>     [php] 
     813>     class articleActions extends sfActions 
     814>     { 
     815>       public function executeCreate(sfWebRequest $request) 
     816>       { 
     817>         $this->form = new ArticleForm(); 
     818>       
     819>         if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('article'))) 
     820>         { 
     821>           $this->redirect('article/created'); 
     822>         } 
     823>       } 
     824>     } 
     825      
     826### Gestione dell'invio di file 
     827 
     828Il metodo `save()` aggiorna automaticamente gli oggetti Propel, ma non può gestire gli elementi collaterali come l'invio di file. 
     829 
     830Vediamo come allegare un file ad ogni articolo. I file sono memorizzati nella cartella `web/uploads` ed un riferimento al percorso del file viene tento nel campo `file` della tabella `article`, come mostrato nel Listato 4-24. 
     831 
     832Listato 4-24 - Schema per la tabella `article` con file associato 
     833 
     834    // config/schema.yml 
     835    propel: 
     836      article: 
     837        // ... 
     838        file: varchar(255) 
     839 
     840Dopo ogni aggiornamento dello schema, devi aggiornare il modello degli oggetti, il database e le relative form: 
     841 
     842    $ ./symfony propel:build-all 
     843 
     844>**CAUTION** 
     845>Ricordati che il task `propel:build-all` cancella tutte le tabelle dello schema e le ricrea. I dati presenti nelle tabelle sono quindi sovrascritti. Per questo è importante creare dei dati di test (`fixtures`) che puoi scaricare ad ogni modifica del modello. 
     846 
     847Il Listato 4-25 mostra come modificare la classe `ArticleForm` per collegare un widget ed un validatore al campo `file`. 
     848 
     849Listato 4-25 - Modifica del campo `file` della form `ArticleForm`. 
     850 
     851    [php] 
     852    class ArticleForm extends BaseArticleForm 
     853    { 
     854      public function configure() 
     855      { 
     856        // ... 
     857      
     858        $this->widgetSchema['file'] = new sfWidgetFormInputFile(); 
     859        $this->validatorSchema['file'] = new sfValidatorFile(); 
     860      } 
     861    } 
     862 
     863Come per tutte le form che consentono l'invio di file, non dimenticare di aggiungere nel template l'attributo `enctype` al tag `form`(vedi il Capitolo 2 per ulteriori informazioni sulla gestione dell'invio dei file). 
     864 
     865Il Listato 4-26 mostra le modifiche da applicare quando si salva la form per caricare il file sul server e memorizzarne il percorso nell'oggetto `article`. 
     866 
     867Listato 4-26 - Salvataggop dell'oggetto `article` e file caricato nell'azione 
     868 
     869    [php] 
     870    public function executeEdit($request) 
     871    { 
     872      $author = ArticlePeer::retrieveByPk($request->getParameter('id')); 
     873      $this->form = new ArticleForm($author); 
     874      
     875      if ($request->isMethod('post')) 
     876      { 
     877        $this->form->bind($request->getParameter('article'), $request->getFiles('article')); 
     878        if ($this->form->isValid()) 
     879        { 
     880          $file = $this->form->getValue('file'); 
     881          $filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension()); 
     882          $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); 
     883      
     884          $article = $this->form->save(); 
     885      
     886          $this->redirect('article/edit?id='.$article->getId()); 
     887        } 
     888      } 
     889    } 
     890 
     891Il salvataggio del file caricato su filesystem consente all'oggetto `sfValidatedFile` di conoscere il percorso assoluto del file. Durante la chiamata al metodo `save()`, i valori dei campi sono usati per aggiornare l'oggetto relativo e, come per il campo `file`, l'oggetto `sfValidatedFile` viene convertito in una stringa grazie al metodo `__toString()`, rimandando il percorso assoluto al file. La colonna `file` della tabella `article` conterrà tale percorso assoluto. 
     892 
     893>**TIP** 
     894>Se vuoi memorizzare il percorso relativo alla cartella `sfConfig::get('sf_upload_dir')`, puoi creare una classe che eredita da `sfValidatedFile` ed usare l'opzione `validated_file_class` per inviare al validatore `sfValidatorFile` il nome della nuova classe. Il validatore restituirà un'istanza della tua classe. Vedremo nel resto di questo capitolo un altro approccio, che consiste nel modificare il valore della colonna `file` prima di salvare l'oggetto nel database. 
     895 
     896### Personalizzare il metodo `save()` 
     897 
     898Abbiamo osservato nella sezione precedente come salvare un file caricato nell'azione `edot`. Uno dei principi della programmazione orientata agli oggetti è la riusabilità del codice, grazie al suo incapsulamento in classi. Invece di duplicare il codice usato per salvare il file in ogni azione usando la form `ArticleForm`, è meglio spostarlo nella classe `ArticleForm`. Il Listato 4-27 mostra come sovrascrivere il metodo `save()` per salvare anche il file ed eventualmente cancellare un file esistente. 
     899 
     900Listato 4-27 - Sovrasacrivere il metodo `save()` della classe `ArticleForm` 
     901 
     902    [php] 
     903    class ArticleForm extends BaseFormPropel 
     904    { 
     905      // ... 
     906      
     907      public function save($con = null) 
     908      { 
     909        if (file_exists($this->getObject()->getFile())) 
     910        { 
     911          unlink($this->getObject()->getFile()); 
     912        } 
     913      
     914        $file = $this->getValue('file'); 
     915        $filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension()); 
     916        $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); 
     917      
     918        return parent::save($con); 
     919      } 
     920    } 
     921 
     922Dopo aver spostato il codice alla form, l'azione `edit` è identica al codice generato inzialmente dal task `propel:generate-crud`. 
     923 
     924>**SIDEBAR** 
     925>Rifattorizzare il codice del modello nella form 
     926
     927>Le azioni generate dal task `propel:generate-crud` non dovrebbero solitamente essere modificate. 
     928
     929>La logica che potresti aggiungere nell'azione `edit`, specialmente durante la serializzazione della form, deve solitamente essere spostata nelle classi del modello o nelle classi delle form. 
     930
     931>Abbiamo appena visto un esempio di rifattorizzazione nella classe della form per considerare il salvataggio di un file caricato. Vediamo un altro esempio legato al modello. La form `ArticleForm` ha un campo `slug`. Abbiamo osservato che questo campo dovrebbe essere calcolato automaticamente dal nome del campo `title` che potrebbe essere eventualmente sovrascritto dall'utente. Questa logica non dipende dalla form. Appartiene piuttosto al modello, come mostrato nel codice seguente: 
     932
     933>     class Article extends BaseArticle 
     934>     { 
     935>       public function save($con = null) 
     936>       { 
     937>         if (!$this->getSlug()) 
     938>         { 
     939>           $this->setSlugFromTitle(); 
     940>         } 
     941>       
     942>         return parent::save($con); 
     943>       } 
     944>       
     945>       protected function setSlugFromTitle() 
     946>       { 
     947>         // ... 
     948>       } 
     949>     } 
     950
     951> Lo scopo principale di queste rifattorizzazione è quello di rispettare la separazione tra i livelli applicativi, e specialmente la riusabilità degli sviluppi.    
     952 
     953### Personalizzare il metodo `doSave()` 
     954 
     955Abbiamo osservato che il salvataggio di un oggetto è stato fatto con una transazione, per poter garantire che ogni operazione legata al salvataggio sia processata correttamente. Quando sovrascriviamo il metodo `save()`, come abbiamo fatto nella sezione precedente, per salvare il file caricato, il codice eseguito è indipendente dalla transazione. 
     956 
     957Il Listato 4-28 mostra come usare il metodo `doSave()` per inserire il nostro codice di salvataggio del file caricato nella transazione globale. 
     958 
     959Listato 4-28 - Sovrascrivere il metodo `doSave()` nella form `ArticleForm` 
     960 
     961    [php] 
     962    class ArticleForm extends BaseFormPropel 
     963    { 
     964      // ... 
     965      
     966      public function doSave($con = null) 
     967      { 
     968        if (file_exists($this->getObject()->getFile())) 
     969        { 
     970          unlink($this->getObject()->getFile()); 
     971        } 
     972      
     973        $file = $this->getValue('file'); 
     974        $filename = sha1($file->getOriginalName()).$file->getExtension($file->getOriginalExtension()); 
     975        $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); 
     976      
     977        return parent::doSave($con); 
     978      } 
     979    } 
     980 
     981Essendo il metodo `doSave()` richiamato dalla transazione creata dal metodo `save()`, se la chiamata al metodo `save()` dell'oggetto `file()` solleva un'eccezione, l'oggetto non viene salvato. 
     982 
     983### Personalizzare il metodo `updateObject()` 
     984 
     985A volte è necessario modificare l'oggetto connesso con la form tra l'aggiornamento ed il salvataggio nel database. 
     986 
     987Nel nostro esempio di invio di file, invece di memorizzare il percorso assoluto del file caricato nella colonna `file`, vogiamo memorizzare il percorso relativo alla cartella `sfConfig::get('sf_upload_dir')`. 
     988 
     989Il Listato 4-29 mostra come sovrascrivere il metodo `updateObject()` della form `ArticleForm` per cambiare il valore della colonna `file` dopo l'aggiornamento automatico ma prima che venga salvato. 
     990 
     991Listato 4-29 - Sovrascrivere il metodo `updateObject()` e la classe `ArticleForm` 
     992 
     993    [php] 
     994    class ArticleForm extends BaseFormPropel 
     995    { 
     996      // ... 
     997      
     998      public function updateObject() 
     999      { 
     1000        $object = parent::updateObject(); 
     1001      
     1002        $object->setFile(str_replace(sfConfig::get('sf_upload_dir').'/', '', $object->getFile())); 
     1003      
     1004        return $object; 
     1005      } 
     1006    } 
     1007 
     1008Il metodo `updateObject()` viene chiamato dal metodo `doSave()` prima di salvare l'oggetto nel database. 
     1009 
     1010}}}