Development

Documentation/it_IT/book/forms/04-Propel-Integration

You must first sign up to be able to contribute.

Version 1 (modified by garak, 9 years ago)
da finire…

Integrazione con Propel

In 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.

Questo 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 All'interno del layer Modello nella guida a symfony.

Prima di cominciare

In 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.

Listato 4-1 - Schema del database

// config/schema.yml
propel:
  article:
    id:           ~
    title:        { type: varchar(255), required: true }
    slug:         { type: varchar(255), required: true }
    content:      longvarchar
    status:       varchar(255)
    author_id:    { type: integer, required: true, foreignTable: author, foreignReference: id, OnDelete: cascade }
    category_id:  { type: integer, required: false, foreignTable: category, foreignReference: id, onDelete: setnull }
    published_at: timestamp
    created_at:   ~
    updated_at:   ~
    _uniques:
      unique_slug: [slug]

  author:
    id:           ~
    first_name:   varchar(20)
    last_name:    varchar(20)
    email:        { type: varchar(255), required: true }
    active:       boolean

  category:
    id:           ~
    name:         { type: varchar(255), required: true }

  tag:
    id:           ~
    name:         { type: varchar(255), required: true }

  article_tag:
    article_id:   { type: integer, foreignTable: article, foreignReference: id, primaryKey: true, onDelete: cascade }
    tag_id:       { type: integer, foreignTable: tag, foreignReference: id, primaryKey: true, onDelete: cascade }

Ecco le relazioni tra le tabelle:

  • Relazione 1-n tra la tabella article e la tabella author: un articolo è scritto da uno ed un solo autore
  • Relazione 1-n tra la tabella article e la tabella category: un articolo appartiene ad una o nessuna categoria
  • Relazione n-n tra le tabelle article e tag

Generazione delle classi delle form

Vogliamo 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:

$ ./symfony propel:build-forms

Durante 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.

NOTE Anche propel:build-all e propel:build-all-load aggiornano le classi delle form, invocando automaticamente il task propel:build-forms.

Dopo aver eseguito questi task, una struttura di file è stata creata nella cartella lib/form/. Ecco i file creati per il nostro schema di esempio:

lib/
  form/
    BaseFormPropel.class.php
    ArticleForm.class.php
    ArticleTagForm.class.php
    AuthorForm.class.php
    CategoryForm.class.php
    TagForm.class.php
    base/
      BaseArticleForm.class.php
      BaseArticleTagForm.class.php
      BaseAuthorForm.class.php
      BaseCategoryForm.class.php
      BaseTagForm.class.php

Il 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.

SIDEBAR Cartella di generazione delle form

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/.

  propel:
    _attributes: { noXsd: false, defaultIdMethod: none, package: lib.model.cms }
    # ...

I package sono utili per suddividere lo schema del database e rilasciare form con un plugin, come vedremo nel Capitolo 5.

Per ulteriori informazioni sui package Propel, fai riferimento al capitolo All'interno del layer Modello nella guida a symfony

La tabella seguente riassume la gerarchia tra le varie classi coinvolte nella definizione della form AuthorForm.

Classe Package Per Descrizione
AuthorForm progetto sviluppatore Sovrascrive la form generata
BaseAuthorForm progetto symfony Basata sullo schema e sovrascritta ad ogni esecuzione di propel:build-forms
BaseFormPropel progetto sviluppatore Consente la personalizzazione globale delle form Propel
sfFormPropel plugin Propel symfony Base delle form Propel
sfForm symfony symfony Base delle form symfony

Per 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.

Listato 4-2 - Classe AuthorForm

[php]
class AuthorForm extends BaseAuthorForm
{
  public function configure()
  {
  }
}

Il Listato 4-3 mostra la classe BaseAuthorForm, con i validatori ed i widget generati tramite l'introspezione del modello per la tabella author.

Listato 4-3 - Classe BaseAuthorForm, che rappresenta la form della tabella author

[php]
class BaseAuthorForm extends BaseFormPropel
{
  public function setup()
  {
    $this->setWidgets(array(
      'id'         => new sfWidgetFormInputHidden(),
      'first_name' => new sfWidgetFormInput(),
      'last_name'  => new sfWidgetFormInput(),
      'email'      => new sfWidgetFormInput(),
    ));

    $this->setValidators(array(
      'id'         => new sfValidatorPropelChoice(array('model' => 'Author', 'column' => 'id', 'required' => false)),
      'first_name' => new sfValidatorString(array('max_length' => 20, 'required' => false)),
      'last_name'  => new sfValidatorString(array('max_length' => 20, 'required' => false)),
      'email'      => new sfValidatorString(array('max_length' => 255)),
    ));

    $this->widgetSchema->setNameFormat('author[%s]');

    $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);

    parent::setup();
  }

  public function getModelName()
  {
    return 'Author';
  }
}

La classe generata assomiglia molto alle form che abbiamo già creato nel capitolo precedente, tranne per alcuni aspetti:

  • La classe base è BaseFormPropel invece di sfForm
  • Le configurazioni del validatore e del widget stanno nel metodo setup() invece che nel metodo configure()
  • Il metodo getModelName() restituisce la classe Propel correlata a questa form

SIDEBAR Personalizzazione globale delle form Propel

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:

abstract class BaseFormPropel extends sfFormPropel
{
  public function setup()
  {
    sfWidgetFormSchema::setDefaultFormFormatterName('div');
  }
}

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.

TIP 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().

I nomei dei campi della form sono identici ai nomi delle colonne dello schema: id, first_name, last_name, email.

Per 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.

Le form generate possono essere usate immediatamente. Aggiungi un'istruzione <?php echo $form ?> e questo consentirà di creare delle form funzionali con validazione senza scrivere una sola riga di codice.

Oltre 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.

Alla 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.

Il generatore CRUD

Ora 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:

$ ./symfony propel:generate-crud frontend author Author

propel:generate-crud accetta tre parametri:

  • frontend : nome dell'applicazione in cui vuoi creare il modulo
  • author : nome del modulo che vuoi creare
  • Author : nome della classe del modello per cui vuoi creare il modulo

NOTE 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.

Nel 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.

Listato 4-4 - La classe authorActions generata dal task

[php]
// apps/frontend/modules/author/actions/actions.class.php
class authorActions extends sfActions
{
  public function executeIndex()
  {
    $this->authorList = AuthorPeer::doSelect(new Criteria());
  }

  public function executeCreate()
  {
    $this->form = new AuthorForm();

    $this->setTemplate('edit');
  }

  public function executeEdit($request)
  {
    $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id')));
  }

  public function executeUpdate($request)
  {
    $this->forward404Unless($request->isMethod('post'));

    $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id')));

    $this->form->bind($request->getParameter('author'));
    if ($this->form->isValid())
    {
      $author = $this->form->save();

      $this->redirect('author/edit?id='.$author->getId());
    }

    $this->setTemplate('edit');
  }

  public function executeDelete($request)
  {
    $this->forward404Unless($author = AuthorPeer::retrieveByPk($request->getParameter('id')));

    $author->delete();

    $this->redirect('author/index');
  }
}

In 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:

$ ./symfony propel:generate-crud frontend author Author --non-atomic-actions

Il codice generato usando --non-atomic-actions (Listato 4-5) è più conciso e meno prolisso.

Listato 4-5 - La classe authorActions generata con l'opzione --non-atomic-actions

[php]
class authorActions extends sfActions
{
  public function executeIndex()
  {
    $this->authorList = AuthorPeer::doSelect(new Criteria());
  }

  public function executeEdit($request)
  {
    $this->form = new AuthorForm(AuthorPeer::retrieveByPk($request->getParameter('id')));

    if ($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter('author'));
      if ($this->form->isValid())
      {
        $author = $this->form->save();

        $this->redirect('author/edit?id='.$author->getId());
      }
    }
  }

  public function executeDelete($request)
  {
    $this->forward404Unless($author = AuthorPeer::retrieveByPk($request->getParameter('id')));

    $author->delete();

    $this->redirect('author/index');
  }
}

Il 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:

$ ./symfony propel:generate-crud frontend author Author --non-verbose-templates

Questa opzione è utile durante la fase di prototipizzazione, come mostra il Listato 4-6.

Listato 4-6 - Il template editSuccess

// apps/frontend/modules/author/templates/editSuccess.class.php
<?php $author = $form->getObject() ?>
<h1><?php echo $author->isNew() ? 'New' : 'Edit' ?> Author</h1>

<form action="<?php echo url_for('author/edit'.(!$author->isNew() ? '?id='.$author->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
  <table>
    <tfoot>
      <tr>
        <td colspan="2">
          &nbsp;<a href="<?php echo url_for('author/index') ?>">Cancel</a>
          <?php if (!$author->isNew()): ?>
            &nbsp;<?php echo link_to('Delete', 'author/delete?id='.$author->getId(), array('post' => true, 'confirm' => 'Are you sure?')) ?>
          <?php endif; ?>
          <input type="submit" value="Save" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form ?>
    </tbody>
  </table>
</form>

TIP L'opzione --with-show ci consente di generare un'azione ed un template che possiamo usare per vedere un oggetto (in sola lettura).

Ora 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.

Figura 4-1 - Lista degli autori

Lista degli autori

Figura 4-2 - Modifica di un autore con errori di validazione

Modifica di un autore con errori di validazione

Possiamo ora ripetere l'operazione con la classe Article:

$ ./symfony propel:generate-crud frontend article Article --non-verbose-templates --non-atomic-actions

Il 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.

Figura 4-3 - Le tabelle collegate devono definire il metodo __toString()

![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()")

La 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.

Listato 4-7 - Implementare il metodo __toString() per la classe Author

[php]
class Author extends BaseAuthor
{
  public function __toString()
  {
    return $this->getFirstName().' '.$this->getLastName();
  }
}

Proprio come la classe Author, puoi creare dei metodi __toString() per le altre classi del nostro modello: Article, Category, Tag.

TIP L'opzione method del widget sfWidgetFormPropelSelect cambia il metodo usato per rappresentare un oggetto in formato testuale.

La Figura 4-4 mostra come creare un articolo dopo aver implementato il metodo __toString().

Figura 4-4 - Creare un articolo

Creare un articolo

(TODO...)