mercoledì 30 dicembre 2009

Lyra, personalizzare il form articoli

Ho lavorato un po' sul form per l'inserimento e modifica degli articoli in Lyra. Infatti fino ad ora la struttura del form è stata quella generata automaticamente da symfony, a parte piccole modifiche. Questo è invece il risultato del lavoro svolto (cliccare l'immagine per ingrandirla).

I campi sono organizzati in blocchi o 'pannelli' e il form si sviluppa su due colonne per sfruttare lo spazio orizzontale. Ecco a grandi linee i passi seguiti per personalizzare l'aspetto del form in questo modo.

Per prima cosa ho creato una classe form formatter come già fatto per il form commenti (vedi Personalizzare l'aspetto dei form in symfony ).

La classe sfWidgetFormSchemaFormatterLyraContent è molto semplice: la proprietà rowFormat serve a stabilire l'ordine degli elementi (etichetta, campo, messaggio di aiuto e messaggio di errore di validazione) all'interno di ogni riga del form; inoltre è stato ridefinito il metodo generateLabel() in modo da aggiungere un attributo di classe particolare (field-bool) a tutti i tag <label> relativi a campi boolean, questo per poter allineare, tramite css, l'etichetta di questi campi in modo diverso rispetto a quelle degli altri campi.

Ci si potrebbe chiedere la ragione di un form formatter per determinare l'apetto di un singolo form. Adesso il form è uno solo, quando saranno gestiti altri tipi di contenuto ognuno avrà il suo form di inserimento e modifica e tutti dovranno avere un aspetto consistente: la classe formatter ci faciliterà questo compito.

Si sono rese necessarie diverse modifiche alla classe LyraArticleForm. Invece di postare tutto il codice qui è preferibile un link al repository che mostri le differenze tra le revisioni 31 e 32 della classe.

La proprietà panels determina l'ordine dei pannelli e i campi contenuti in ciascun pannello, la proprietà break_at determina l'inizio della seconda colonna: entrambe vengono impostate nel metodo configure() e utilizzate nel template (_form.php).

Il pannello Etichette viene gestito in modo particolare. Visto che è l'utente che sceglie quali e quanti cataloghi possono essere utilizzati per categorizzare ogni tipo di contenuto, come si è visto a suo tempo, le liste di selezione delle etichette devono essere generate dinamicamente. Ho rimosso questa parte di codice dalla classe LyraArticleForm e creato un form dedicato (classe LyraLabelListsForm) che viene incluso nel form principale da queste istruzioni

lib/form/doctrine/LyraArticleForm.class.php

class LyraArticleForm extends BaseLyraArticleForm
{
...
  public function configure()
  {
...
    $label_lists_form = new LyraLabelListsForm(array(), array('ctype_id' => $ctype_id, 'selected' => $selected));
    $this->embedForm('labels', $label_lists_form);
...
  }
}

Demandare la creazione delle liste di selezione delle etichette ad una classe separata ci permetterà di evitare duplicazioni di codice quando sarà necessario gestire altri tipi di contenuto in aggiunta agli articoli. Il metodo saveLabels() è stato modificato per tenere conto di questa modifica.

Non è difficile ottenere lo stesso aspetto del form anche nel backend. L'admin generator di symfony è predisposto per suddividere i campi dei form in fieldset impostati nel file generator.yml. Per vedere come basta confrontare le differenze del file nelle revisioni 31 e 32. Non resta che modificare il template per utilizzare i fieldset come pannelli disposti su due colonne: _form.php.

Oltre a tutto questo ho aggiunto un campo ctype_id alla tabella articoli (modello LyraArticle) che viene utilizzato in schema.yml come chiave di una relazione ContentType che lega articoli e tipi di contenuto. Mi rendo conto che al momento non se ne capisce molto l'utilità in quanto tutti i record della tabella articoli hanno lo stesso tipo di contenuto ed i tipi di contenuto che saranno creati in seguito avranno proprie tabelle. Vedremo però che ci sono casi in cui tornerà comodo avere l'ID del tipo di contenuto sui singoli record.

Per il momento l'immediata conseguenza di questa modifica è la necessità per chi si allinea alla revisione 32 di eseguire dopo il checkout i comandi

./symfony doctrine:build --all --and-load
./symfony cc

La sintassi del comando doctrine:build è quella propria di symfony 1.3 e 1.4, come già detto la vecchia sintassi (doctrine:build-all-reload) è ancora valida in symfony 1.3, ma non lo sarà più dalla versione 1.4 per cui è bene abbandonarla subito.

La spiegazione delle modifiche è stata sintetica, chi abbia bisogno di qualsiasi chiarimento può lasciare un commento (finisco anche in rima). Al prossimo anno.

mercoledì 23 dicembre 2009

Aggiornato Lyra a symfony 1.3

Ho aggiornato Lyra a symfony 1.3.1. Per farlo ho cancellato la cartella symfony dal repository Google Code ed impostato la proprietà svn:externals su lib/vendor a questo valore:

symfony http://svn.symfony-project.com/tags/RELEASE_1_3_1/

In effetti inizialmente avevo importato nel repository l'intera copia locale del progetto incluso il framework. Ma continuando in questo modo sarei stato costretto a fare il commit dell'intero framework ad ogni aggiornamento di symfony, con perdita di tempo e spreco di spazio sul repository. Visto che non è certo mia intenzione fare modifiche al framework è molto meglio utilizzare svn:externals in modo da ricevere il contenuto di lib/vendor/symfony direttamente dal repository di symfony. Questa è anche la procedura raccomandata dalla documentazione ufficiale.

Aggiornato il framework, ho seguito le istruzioni riportate sul sito di symfony per il passaggio di un progetto dalla versione 1.2 alla 1.3 ed eseguito dalla cartella principale dell'applicazione il comando

./symfony project:upgrade1.3

Ho subito ricevuto un errore! Riporto la causa perché può capitare a chi si trovi nella stessa situazione. Visto che per Lyra utilizzo Doctrine, nella classe ProjectConfiguration era presente questa istruzione per disabilitare il plugin Propel.

config/ProjectConfiguration.class.php
...
class ProjectConfiguration extends sfProjectConfiguration
{
...
$this->disablePlugins(array('sfPropelPlugin'));
...
}

Dato che in symfony 1.3 Propel non è più l'ORM predefinito, questa riga va rimossa altrimenti la disabilitazione di un plugin non abilitato produce un errore che fa abortire il task project:upgrade1.3.

Una volta risolto questo problema la procedura di aggiornamento è stata portata a termine correttamente. Quindi è bastato ricostruire le classi del modello con

./symfony doctrine:build --all-classes
./symfony cc

Sono state poi necessarie alcune piccole correzioni dovute alla nuova versione di Doctrine (symfony 1.3 utilizza Doctrine 1.2). Chi fosse interessato ai dettagli può consultare il log SVN relativo alla revisione 31: i file modificati sono molti, ma la maggior parte delle modifiche sono state fatte automaticamente dal task project:upgrade1.3. L'aggiornamento, a parte il primo intoppo iniziale, è stato molto semplice.

A chi volesse provare la nuova versione, dopo il checkout dal repository consiglierei di ricreare non solo le classi, ma anche di azzerare e generare il database ricaricando i dati di esempio. Almeno è quello che ho fatto io con

./symfony doctrine:build --all --and-load

il comando è equivalente a

./symfony doctrine:build-all-reload

che per il momento può essere ancora utilizzato anche se è bene iniziare ad usare la nuova sintassi che sarà l'unica accettata da symfony 1.4. L'elenco dei nuovi comandi 'build' di Doctrine si ottiene con

./symfony help doctrine:build

Alla fine non fa male la pulizia della cache

./symfony cc

Al più presto sarà fatto l'aggiornamento a symfony 1.4.

giovedì 17 dicembre 2009

symfony, metodi 'magici' find di Doctrine

Doctrine offre la possibilità di interrogare una tabella del database attraverso metodi speciali definiti 'magic finders'. Vediamo alcuni esempi che utilizzano il database di Lyra. Ad esempio per interrogare la tabella articles (classe LyraArticle) per prima cosa si ottiene un'istanza della classe Doctrine_Table

$dt = Doctrine::getTable('LyraArticle');

Il caso più semplice è la ricerca di un record attraverso la chiave primaria

$article = $dt->find(1);

$article contiene il record con chiave primaria (nel nostro caso il campo ID di articles) uguale a 1. Per effettuare una ricerca su un campo diverso dalla chiave primaria si utilizza la variante findBynomecampo

$articles = $dt->findByIsActive(true);

$articles contiene l'insieme degli articoli pubblicati (campo is_active è true). Da notare che quando, come in questo caso, il nome del campo è composto da più parole separate dal carattere sottolineato, il nome del metodo si crea utilizzando il camel case (campo: is_active, metodo: findByIsActive()).

Si può effettuare una ricerca su più campi, ad esempio per ottenere tutti gli articoli pubblicati (is_active true) ed impostati per apparire in prima pagina (is_featured true)

$articles = $dt->findByIsActiveAndIsFeatured(true, true);

Oltre ad AND si può utilizzare anche OR.

I metodi findBy restituiscono un insieme di record (Doctrine_Collection), quando si preferisce che venga restituito un singolo oggetto record (Doctrine_Record) si deve utilizzare findOneBynomecampo, ad esempio

$article = $dt->findOneByTitle('Titolo articolo');

Anche se esistesse più di un articolo con titolo 'Titolo articolo' solo il primo record sarebbe restituito in $article.

Abbiamo anche la possibilità di ottenere array invece che oggetti come valore di ritorno da ogni metodo 'magic finder'. In base alla scelta cambia naturalmente il modo di accedere ai valori dei campi. Ad esempio

$articles = Doctrine::getTable('LyraArticle')
  ->FindByIsActive(true);

$articles è una collezione di oggetti, quindi i valori dei campi si ottengono come proprietà dell'oggetto record.

foreach($articles as $a) {
  echo $a->title;
}

Alternativamente

$articles = Doctrine::getTable('LyraArticle')
      ->FindByIsActive(true, Doctrine::HYDRATE_ARRAY);

$articles è un array ed i valori dei singoli campi si ottengono tramite indice.

foreach($articles as $a) {
  echo $a['title'];
}

giovedì 10 dicembre 2009

Demo Lyra online

Ho messo online un mini sito dimostrativo di Lyra. Il demo è limitato al frontend, in seguito mi ripropongo di creare un account utente di prova per consentire a chi lo desideri di giocare un po' anche con il backend. Per fare questo devo lavorare ancora sulla gestione dei permessi in quanto l'unico tipo di utente disponibile al momento è il super-amministratore al cui account, per ovvie ragioni, non posso dare accesso pubblico.

Naturalmente chi ha già familiarità con symfony o ha avuto la pazienza di seguire gli articoli pubblicati fino a questo momento, può fare il checkout dal repository di Google Code e testare l'intera applicazione in locale.

Questo è l'indirizzo

http://lyra.latenight-coding.com/

Con l'occasione ho aggiunto un componente archive per visualizzare un archivio mensile degli articoli (vedere menù nella colonna destra), modificato la 'byline' dell'articolo per includere l'autore oltre alla data e fatto altri aggiustamenti. Tutto si trova nelle revisioni 24, 25, 26, 27.