<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10italianfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;CEAESXgyeCp7ImA9WxBREUs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007</id><updated>2009-12-30T09:45:08.690+01:00</updated><title>Sviluppare in Rete</title><subtitle type="html">Parliamo di sviluppo di siti e applicazioni web.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>97</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/sviluppare-in-rete" /><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fsviluppare-in-rete" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.bloglines.com/sub/http://feeds.feedburner.com/sviluppare-in-rete" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fsviluppare-in-rete" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fsviluppare-in-rete" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fsviluppare-in-rete" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://add.my.yahoo.com/content?lg=it&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2Fsviluppare-in-rete" src="http://eur.i1.yimg.com/eur.yimg.com/i/it/my/mioya1.gif">Subscribe with Mio Yahoo!</feedburner:feedFlare><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;CEAESXk6fCp7ImA9WxBREUs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-978780039034225101</id><published>2009-12-30T09:43:00.001+01:00</published><updated>2009-12-30T09:45:08.714+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-30T09:45:08.714+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, personalizzare il form articoli</title><content type="html">&lt;p&gt;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).&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s1600-h/form-articolo.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 311px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s320/form-articolo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5420947125712561426" /&gt;&lt;/a&gt;
&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Per prima cosa ho creato una classe &lt;strong&gt;form formatter&lt;/strong&gt; come già fatto per il form commenti (vedi &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/personalizzare-form-in-symfony.html"&gt;Personalizzare l'aspetto dei form in symfony&lt;/a&gt; ). &lt;/p&gt;

&lt;p&gt;La classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/sfWidgetFormSchemaFormatterLyraContent.php?r=32"&gt;sfWidgetFormSchemaFormatterLyraContent&lt;/a&gt; è molto semplice: la proprietà &lt;em&gt;rowFormat&lt;/em&gt; 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 &lt;strong&gt;generateLabel()&lt;/strong&gt; in modo da aggiungere un attributo di classe particolare (&lt;em&gt;field-bool&lt;/em&gt;) a tutti i tag &amp;lt;label&amp;gt; relativi a campi boolean, questo per poter allineare, tramite css, l'etichetta di questi campi in modo diverso rispetto a quelle degli altri campi.&lt;/p&gt;

&lt;p&gt;Ci si potrebbe chiedere la ragione di un &lt;strong&gt;form formatter&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Si sono rese necessarie diverse modifiche alla classe &lt;strong&gt;LyraArticleForm&lt;/strong&gt;. Invece di postare tutto il codice qui è preferibile un link al repository che mostri le differenze tra le &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/lib/form/doctrine/LyraArticleForm.class.php"&gt;revisioni 31 e 32 della classe.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La proprietà &lt;em&gt;panels&lt;/em&gt; determina l'ordine dei pannelli e i campi contenuti in ciascun pannello, la proprietà &lt;em&gt;break_at&lt;/em&gt; determina l'inizio della seconda colonna: entrambe vengono impostate nel metodo &lt;strong&gt;configure()&lt;/strong&gt; e utilizzate nel template (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/frontend/modules/article/templates/_form.php?r=32"&gt;_form.php&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;LyraArticleForm&lt;/strong&gt; e creato un form dedicato (classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraLabelListsForm.class.php?r=32"&gt;LyraLabelListsForm&lt;/a&gt;) che viene incluso nel form principale da queste istruzioni&lt;/p&gt;

&lt;pre&gt;
lib/form/doctrine/LyraArticleForm.class.php

class LyraArticleForm extends BaseLyraArticleForm
{
...
  public function configure()
  {
...
    $label_lists_form = new LyraLabelListsForm(array(), array('ctype_id' =&amp;gt; $ctype_id, 'selected' =&amp;gt; $selected));
    $this-&amp;gt;embedForm('labels', $label_lists_form);
...
  }
}
&lt;/pre&gt;
&lt;p&gt;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 &lt;strong&gt;saveLabels()&lt;/strong&gt; è stato modificato per tenere conto di questa modifica.&lt;/p&gt;

&lt;p&gt;Non è difficile ottenere lo stesso aspetto del form anche nel backend. L'admin generator di symfony è predisposto per suddividere i campi dei form in &lt;em&gt;fieldset&lt;/em&gt; impostati nel file &lt;em&gt;generator.yml&lt;/em&gt;. Per vedere come basta confrontare le differenze del file nelle &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/apps/backend/modules/article/config/generator.yml"&gt;revisioni 31 e 32&lt;/a&gt;. Non resta che modificare il template per utilizzare i fieldset come pannelli disposti su due colonne: &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/generator/sfDoctrineModule/admin/template/templates/_form.php?r=32"&gt;_form.php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Oltre a tutto questo ho aggiunto un campo &lt;strong&gt;ctype_id&lt;/strong&gt; alla tabella articoli (modello &lt;strong&gt;LyraArticle&lt;/strong&gt;) che viene utilizzato in &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/config/doctrine/schema.yml"&gt;schema.yml&lt;/a&gt; come chiave di una relazione &lt;strong&gt;ContentType&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Per il momento l'immediata conseguenza di questa modifica è la necessità per chi si allinea alla &lt;strong&gt;revisione 32&lt;/strong&gt; di eseguire dopo il checkout i comandi&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build --all --and-load
./symfony cc
&lt;/pre&gt;

&lt;p&gt;La sintassi del comando &lt;strong&gt;doctrine:build&lt;/strong&gt; è quella propria di symfony 1.3 e 1.4, come già detto la vecchia sintassi (&lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt;) è ancora valida in symfony 1.3, ma non lo sarà più dalla versione 1.4 per cui è bene abbandonarla subito.&lt;/p&gt;
 
&lt;p&gt;La spiegazione delle modifiche è stata sintetica, chi abbia bisogno di qualsiasi chiarimento può lasciare un commento (finisco anche in rima). Al prossimo anno.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-978780039034225101?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/SAqjlxoHLOQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/978780039034225101/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/lyra-personalizzare-il-form-articoli.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/978780039034225101?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/978780039034225101?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/lyra-personalizzare-il-form-articoli.html" title="Lyra, personalizzare il form articoli" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s72-c/form-articolo.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;D0QMQXo9cCp7ImA9WxBSFUg.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1916208773667093528</id><published>2009-12-23T09:03:00.001+01:00</published><updated>2009-12-23T09:03:00.468+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-23T09:03:00.468+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Aggiornato Lyra a symfony 1.3</title><content type="html">&lt;p&gt;Ho aggiornato &lt;strong&gt;Lyra&lt;/strong&gt; a &lt;strong&gt;symfony 1.3.1&lt;/strong&gt;. Per farlo ho cancellato la cartella &lt;em&gt;symfony&lt;/em&gt; dal repository Google Code ed impostato la proprietà &lt;strong&gt;svn:externals&lt;/strong&gt; su &lt;em&gt;lib/vendor&lt;/em&gt; a questo valore:&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;symfony http://svn.symfony-project.com/tags/RELEASE_1_3_1/&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;svn:externals&lt;/strong&gt; in modo da ricevere il contenuto di &lt;em&gt;lib/vendor/symfony&lt;/em&gt; direttamente dal repository di symfony. Questa è anche la procedura raccomandata dalla documentazione ufficiale.&lt;/p&gt;

&lt;p&gt;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&lt;/p&gt;

&lt;pre&gt;&lt;p&gt;./symfony project:upgrade1.3&lt;/p&gt;&lt;/pre&gt;

&lt;p&gt;Ho subito ricevuto un errore! Riporto la causa perché può capitare a chi si trovi nella stessa situazione. Visto che per Lyra utilizzo &lt;strong&gt;Doctrine&lt;/strong&gt;, nella classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/config/ProjectConfiguration.class.php?r=23"&gt;ProjectConfiguration&lt;/a&gt; era presente questa istruzione per disabilitare il plugin &lt;strong&gt;Propel&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
config/ProjectConfiguration.class.php
...
class ProjectConfiguration extends sfProjectConfiguration
{
...
$this-&amp;gt;disablePlugins(array('sfPropelPlugin'));
...
}
&lt;/pre&gt;
&lt;p&gt;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 &lt;strong&gt;project:upgrade1.3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Una volta risolto questo problema la procedura di aggiornamento è stata portata a termine correttamente. Quindi è bastato ricostruire le classi del modello con&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build --all-classes
./symfony cc
&lt;/pre&gt;

&lt;p&gt;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 &lt;strong&gt;revisione 31&lt;/strong&gt;: i file modificati sono molti, ma la maggior parte delle modifiche sono state fatte automaticamente dal task &lt;strong&gt;project:upgrade1.3&lt;/strong&gt;. L'aggiornamento, a parte il primo intoppo iniziale, è stato molto semplice.&lt;/p&gt;

&lt;p&gt;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&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build --all --and-load&lt;/pre&gt;

&lt;p&gt;il comando è equivalente a&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-all-reload&lt;/pre&gt;

&lt;p&gt;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&lt;/p&gt;

&lt;pre&gt;./symfony help doctrine:build&lt;/pre&gt;

&lt;p&gt;Alla fine non fa male la pulizia della cache&lt;/p&gt;

&lt;pre&gt;./symfony cc&lt;/pre&gt;

&lt;p&gt;Al più presto sarà fatto l'aggiornamento a symfony 1.4.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1916208773667093528?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/qF_TBtIamH0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1916208773667093528/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/aggiornato-lyra-symfony-13.html#comment-form" title="3 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1916208773667093528?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1916208773667093528?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/aggiornato-lyra-symfony-13.html" title="Aggiornato Lyra a symfony 1.3" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></entry><entry gd:etag="W/&quot;A0ANQn8yeCp7ImA9WxBSEEU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3570149999050753197</id><published>2009-12-17T23:36:00.002+01:00</published><updated>2009-12-17T23:43:13.190+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-17T23:43:13.190+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, metodi 'magici' find di Doctrine</title><content type="html">&lt;p&gt;&lt;strong&gt;Doctrine&lt;/strong&gt; offre la possibilità di interrogare una tabella del database attraverso metodi speciali definiti 'magic finders'. Vediamo alcuni esempi che utilizzano il database di &lt;strong&gt;Lyra&lt;/strong&gt;. Ad esempio per interrogare la tabella &lt;em&gt;articles&lt;/em&gt; (classe &lt;strong&gt;LyraArticle&lt;/strong&gt;) per prima cosa si ottiene un'istanza della classe &lt;strong&gt;Doctrine_Table&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;$dt = Doctrine::getTable('LyraArticle');&lt;/pre&gt;

&lt;p&gt;Il caso più semplice è la ricerca di un record attraverso la chiave primaria&lt;/p&gt;

&lt;pre&gt;$article = $dt-&amp;gt;find(1);&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;$article&lt;/strong&gt; contiene il record con chiave primaria (nel nostro caso il campo ID di &lt;em&gt;articles&lt;/em&gt;) uguale a 1. Per effettuare una ricerca su un campo diverso dalla chiave primaria si utilizza la variante &lt;strong&gt;findBy&lt;/strong&gt;&lt;em&gt;nomecampo&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;$articles = $dt-&amp;gt;findByIsActive(true);&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; contiene l'insieme degli articoli pubblicati (campo &lt;strong&gt;is_active&lt;/strong&gt; è &lt;em&gt;true&lt;/em&gt;). 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 &lt;em&gt;camel case&lt;/em&gt; (campo: &lt;strong&gt;is_active&lt;/strong&gt;, metodo: &lt;strong&gt;findByIsActive()&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Si può effettuare una ricerca su più campi, ad esempio per ottenere tutti gli articoli pubblicati (&lt;strong&gt;is_active&lt;/strong&gt; &lt;em&gt;true&lt;/em&gt;) ed impostati per apparire in prima pagina (&lt;strong&gt;is_featured&lt;/strong&gt; &lt;em&gt;true&lt;/em&gt;)&lt;/p&gt;

&lt;pre&gt;$articles = $dt-&amp;gt;findByIsActiveAndIsFeatured(true, true);&lt;/pre&gt;

&lt;p&gt;Oltre ad &lt;strong&gt;AND&lt;/strong&gt; si può utilizzare anche &lt;strong&gt;OR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I metodi &lt;strong&gt;findBy&lt;/strong&gt; restituiscono un insieme di record (&lt;strong&gt;Doctrine_Collection&lt;/strong&gt;), quando si preferisce che venga restituito un singolo oggetto record (&lt;strong&gt;Doctrine_Record&lt;/strong&gt;) si deve utilizzare &lt;strong&gt;findOneBy&lt;/strong&gt;&lt;em&gt;nomecampo&lt;/em&gt;, ad esempio&lt;/p&gt;

&lt;pre&gt;$article = $dt-&amp;gt;findOneByTitle('Titolo articolo');&lt;/pre&gt;

&lt;p&gt;Anche se esistesse più di un articolo con titolo 'Titolo articolo' solo il primo record sarebbe restituito in &lt;strong&gt;$article&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;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&lt;/p&gt;

&lt;pre&gt;
$articles = Doctrine::getTable('LyraArticle')
  -&amp;gt;FindByIsActive(true);
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; è una collezione di oggetti, quindi i valori dei campi si ottengono come proprietà dell'oggetto record.&lt;/p&gt;
&lt;pre&gt;
foreach($articles as $a) {
  echo $a-&gt;title;
}
&lt;/pre&gt;
&lt;p&gt;Alternativamente&lt;/p&gt;
&lt;pre&gt;
$articles = Doctrine::getTable('LyraArticle')
      -&amp;gt;FindByIsActive(true, Doctrine::HYDRATE_ARRAY);
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; è un array ed i valori dei singoli campi si ottengono tramite indice.&lt;/p&gt;
&lt;pre&gt;
foreach($articles as $a) {
  echo $a['title'];
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3570149999050753197?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/85IBB9N1-3o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3570149999050753197/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/symfony-metodi-magici-find-di-doctrine.html#comment-form" title="2 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3570149999050753197?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3570149999050753197?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/symfony-metodi-magici-find-di-doctrine.html" title="symfony, metodi 'magici' find di Doctrine" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></entry><entry gd:etag="W/&quot;Dk8GRncyfyp7ImA9WxBTFE4.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-354838157107144814</id><published>2009-12-10T09:45:00.001+01:00</published><updated>2009-12-10T09:47:07.997+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-10T09:47:07.997+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Demo Lyra online</title><content type="html">&lt;p&gt;Ho messo online un mini sito dimostrativo di &lt;strong&gt;Lyra&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;Naturalmente chi ha già familiarità con &lt;strong&gt;symfony&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Questo è l'indirizzo&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lyra.latenight-coding.com/"&gt;http://lyra.latenight-coding.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Con l'occasione ho aggiunto un componente &lt;em&gt;archive&lt;/em&gt; 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.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-354838157107144814?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/HtIfGmc1kiw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/354838157107144814/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/demo-lyra-online.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/354838157107144814?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/354838157107144814?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/demo-lyra-online.html" title="Demo Lyra online" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CUUAQnk5eSp7ImA9WxNaEUg.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5616163960148133439</id><published>2009-11-25T13:41:00.000+01:00</published><updated>2009-11-25T13:47:23.721+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-25T13:47:23.721+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, gestione utenti backend</title><content type="html">&lt;p&gt;Arrivati a questo punto è necessario iniziare lo sviluppo delle funzione di gestione utenti. Chi ha scaricato l'applicazione dal repository avrà notato che non viene richiesto alcun login per accedere al backend, con la gestione gestione utenti saranno implementate le funzioni di controllo dell'accesso sia al backend che a quelle azioni del frontend che richiedono un'autenticazione (ad esempio inserimento e modifica articoli).&lt;/p&gt;

&lt;p&gt;Seguirò le linee del tutorial ufficiale che prevede di utilizzare il plugin &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt;. Per prima cosa si installa il plugin con&lt;/p&gt;
&lt;pre&gt;
./symfony plugin:install sfDoctrineGuardPlugin
&lt;/pre&gt;

&lt;p&gt;Si attiva il plugin.&lt;/p&gt;

&lt;pre&gt;
config/ProjectConfiguration.class.php

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this-&amp;gt;enablePlugins(array('sfDoctrinePlugin','sfDoctrineGuardPlugin'));
    $this-&amp;gt;disablePlugins(array('sfPropelPlugin'));
  }
}
&lt;/pre&gt;
&lt;p&gt;Questo almeno è come ho fatto io. Se nel metodo &lt;strong&gt;setup()&lt;/strong&gt; della classe  &lt;strong&gt;ProjectConfiguration&lt;/strong&gt; si utilizza il metodo &lt;strong&gt;enableAllPluginsExcept()&lt;/strong&gt; può non essere necessario abilitare singolarmente il plugin.&lt;/p&gt;

&lt;p&gt;Si generano le classi del modello messe a disposizione dal plugin e si pulisce la cache di symfony come al solito quando si creano nuove classi.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;

&lt;p&gt;Poi si modifica la classe &lt;strong&gt;myUser&lt;/strong&gt; e la si fa derivare dalla classe base che ci viene fornita dal plugin.&lt;/p&gt;

&lt;pre&gt;
apps/backend/lib/myUser.class.php

class myUser extends sfGuardSecurityUser
{
}
&lt;/pre&gt;

Nella configurazione si abilitano i moduli di &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; e si imposta l'azione che viene eseguita quando un utente richiede il login (&lt;strong&gt;signin&lt;/strong&gt;).

&lt;pre&gt;
apps/backend/config/settings.yml
...
all:
  .settings:
    enabled_modules: [default, sfGuardAuth, sfGuardUser, sfGuardGroup]
    ...
  .actions:
    login_module: sfGuardAuth
    login_action: signin
...
&lt;/pre&gt;

&lt;p&gt;Adesso abbiamo quello che ci serve per attivare la funzione di login al backend.&lt;/p&gt;

&lt;pre&gt;
apps/backend/config/security.yml

default:
  is_secure: on
&lt;/pre&gt;

&lt;p&gt;Naturalmente a questo punto bisogna creare almeno un utente, lo si può fare da linea di comando con&lt;/p&gt;

&lt;pre&gt;./symfony guard:create-user admin admin&lt;/pre&gt;

&lt;p&gt;Fino a che restiamo in locale possiamo utilizzare &lt;em&gt;admin&lt;/em&gt; come nome utente e password. Promuoviamo l'utente a superamministratore.&lt;/p&gt;

&lt;pre&gt;./symfony guard:promote admin&lt;/pre&gt;

&lt;p&gt;Il layout (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/backend/templates/layout.php"&gt;layout.php&lt;/a&gt; in &lt;em&gt;apps/backend/templates&lt;/em&gt;) del backend deve essere modificato per impedire la visualizzazione del menù principale agli utenti non autenticati ed aggiungervi un link per il logout e i link alla gestione utenti e gruppi utente che sono messe a disposizione dai moduli &lt;strong&gt;sfGuardUser&lt;/strong&gt; e &lt;strong&gt;sfGuardGroup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Questi moduli possono essere personalizzati con lo stesso metodo utilizzato per i moduli generati nell'applicazione impostando cioè i parametri di configurazione nel file&lt;em&gt;generator.yml&lt;/em&gt;. Però non conviene modificare direttamente i file installati dal plugin in &lt;em&gt;plugins/sfDoctrineGuardPlugin&lt;/em&gt; e sottocartelle, ma creare una cartella per ogni modulo in &lt;em&gt;apps/backend/modules&lt;/em&gt; e inserirvi le personalizzazioni volute. In questo modo potremo fare gli aggiornamenti di &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; senza sovrascrivere file che abbiamo modificato.&lt;/p&gt;

&lt;p&gt;Ho creato una cartella &lt;em&gt;apps/backend/modules/sfGuardUser/config&lt;/em&gt; e vi ho copiato il file &lt;em&gt;generator.yml&lt;/em&gt; che si trova in &lt;em&gt;plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config&lt;/em&gt;. Ho quindi modificato il file per personalizzare l'aspetto della lista utenti.&lt;/p&gt;

&lt;p&gt;La stessa cosa è stata fatta per il modulo &lt;strong&gt;sfGuardGroup&lt;/strong&gt;. Le personalizzazioni al momento sono minime, il contenuto dei file può essere consultato sul repository. In modo analogo è possibile personalizzare la classe actions e i template, esempi si trovano nella documentazione di sfDoctrineGuardPlugin.&lt;/p&gt;

&lt;p&gt;Ho creato un file &lt;em&gt;data/fixtures/users.yml&lt;/em&gt; per l'inserimento di un utente superamministratore nei dati di esempio. In questo modo durante lo sviluppo non si è costretti ad eseguire i comandi &lt;strong&gt;guard:create-user&lt;/strong&gt; e &lt;strong&gt;guard:promote&lt;/strong&gt; ogni volta che si ricreano le tabelle con &lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
data/fixtures/users.yml

sfGuardUser:
  admin:
    username:       admin
    password:       admin
    is_super_admin: true
&lt;/pre&gt;
&lt;p&gt;Ho inoltre definito le relazioni articolo-utente &lt;/p&gt;
&lt;pre&gt;
config/doctrine/schema.yml

LyraArticle:
...
  relations:
    ...
    ArticleCreatedBy:
      class: sfGuardUser
      local: created_by
      foreign: id
      foreignAlias: CreatedArticles
    ArticleUpdatedBy:
      class: sfGuardUser
      local: updated_by
      foreign: id
      foreignAlias: UpdatedArticles
&lt;/pre&gt;
&lt;p&gt;e relazioni analoghe nella tabella etichette&lt;/p&gt;

&lt;pre&gt;
LyraLabel:
...
  relations:
      ...
      LabelCreatedBy:
        class: sfGuardUser
        local: created_by
        foreign: id
        foreignAlias: CreatedLabels
      LabelUpdatedBy:
        class: sfGuardUser
        local: updated_by
        foreign: id
        foreignAlias: UpdatedLabels
&lt;/pre&gt; 

&lt;p&gt;I file &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/fixtures/articles.yml"&gt;articles.yml&lt;/a&gt; e &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/fixtures/labels.yml"&gt;labels.yml&lt;/a&gt; in &lt;em&gt;data/fixtures&lt;/em&gt; sono stati modificati in modo che quando si creano i dati di esempio le chiavi esterne dei record siano valorizzate opportunamente in base alle relazioni appena definite.&lt;/p&gt;

&lt;p&gt;Tutto questo è incluso nella &lt;strong&gt;revisione 23&lt;/strong&gt;. Visto che abbiamo modificato &lt;em&gt;schema.yml&lt;/em&gt; e i file dei dati di esempio, dopo il checkout è necessario eseguire:&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;

&lt;p&gt;A questo punto possiamo effettuare il login al backend.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Login e password: admin.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5616163960148133439?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/3pIsuEzkO0w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5616163960148133439/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-utenti-backend.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5616163960148133439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5616163960148133439?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-utenti-backend.html" title="Lyra, gestione utenti backend" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;A0IFSXk9cCp7ImA9WxNbGUs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6581352395802748996</id><published>2009-11-23T10:43:00.000+01:00</published><updated>2009-11-23T10:45:18.768+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-23T10:45:18.768+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, backend cataloghi ed etichette</title><content type="html">&lt;p&gt;Credo sia chiaro a chiunque abbia seguito gli articoli precedenti che lo sviluppo del backend di un'applicazione symfony segue linee abbastanza standard: le funzioni base di ogni modulo come la visualizzazione dell'elenco record e le funzioni di inserimento, modifica, cancellazione sono generate automaticamente con il comando &lt;strong&gt;doctrine:generate-admin&lt;/strong&gt;; le personalizzazioni per la maggior parte si realizzano impostando parametri nel file di configurazione (&lt;em&gt;generator.yml&lt;/em&gt; nella cartella &lt;em&gt;config&lt;/em&gt; del modulo) anche se è naturalmente possibile scrivere proprio codice per realizzare funzionalità aggiuntive o che comunque si discostano dallo standard.&lt;/p&gt;

&lt;p&gt;Per questo motivo non mi soffermerò nei dettagli dello sviluppo della parte restante del backend di Lyra perché si finirebbe per ripetere quanto già detto in precedenza. Di volta in volta metterò in evidenza solo gli aspetti su cui a mio parere vale la pena soffermarsi, d'altra parte il codice completo è sempre disponibile nel repository su Google Code.&lt;/p&gt;

&lt;h2&gt;Gestione cataloghi&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:generate-admin backend LyraCatalog --module=catalog&lt;/pre&gt;

&lt;p&gt;Dopo la consueta personalizzazione di &lt;em&gt;generator.yml&lt;/em&gt; si ottiene questo risultato.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s1600/backend-cataloghi.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 158px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s320/backend-cataloghi.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5407100187057231058" /&gt;&lt;/a&gt;
&lt;p&gt;Il contenuto delle colonne &lt;strong&gt;Etichette&lt;/strong&gt; e &lt;strong&gt;Pubblicato&lt;/strong&gt; è generato da due partial (&lt;em&gt;_labels.php&lt;/em&gt; e &lt;em&gt;_published.php&lt;/em&gt; in &lt;em&gt;apps/backend/modules/catalog/templates&lt;/em&gt;): il primo visualizza il contatore delle etichette inserite nel catalogo e mostra un link per accedere alla relativa lista.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/catalog/templates/_labels.php

&amp;lt;?php
    echo link_to($lyra_catalog-&amp;gt;countLabels()-1,'@lyra_label_label?id='.$lyra_catalog-&amp;gt;getId());
?&amp;gt;&amp;amp;nbsp;(&amp;lt;?php echo link_to(__('LINK_SHOW_LABELS'),'@lyra_label_label?id='.$lyra_catalog-&amp;gt;getId());?&amp;gt;)
&lt;/pre&gt;

&lt;p&gt;Il secondo genera i link per le icone usate per modificare lo stato pubblicato / non pubblicato del catalogo e funziona nel modo già visto per la gestione articoli e commenti.&lt;/p&gt;

&lt;p&gt;La configurazione del form per l'inserimento e modifica di un catalogo (classe &lt;strong&gt;LyraCatalogForm&lt;/strong&gt;) non presenta particolarità. Ogni catalogo potrà essere associato ad uno o più tipi di contenuto, selezionabili da una lista di checkbox nel form; non è necessario scrivere codice particolare per creare l'insieme delle checkbox in quanto Doctrine genera automaticamente un widget &lt;strong&gt;sfWidgetFormDoctrineChoiceMany&lt;/strong&gt; in base alla relazione cataloghi - tipi di contenuto definita in &lt;em&gt;schema.yml&lt;/em&gt;. Basta impostare l'attributo &lt;strong&gt;expanded&lt;/strong&gt; a &lt;em&gt;true&lt;/em&gt; in &lt;strong&gt;configure()&lt;/strong&gt; per avere un'insieme di checkbox al posto della lista di selezione.&lt;/p&gt;

&lt;p&gt;Quando si crea un nuovo catalogo viene anche inserito nella tabella etichette un record che costituisce la 'radice' dell'albero delle etichette appartenenti al catalogo. Questo viene fatto nel metodo &lt;strong&gt;doSave()&lt;/strong&gt; di &lt;strong&gt;LyraCatalogForm&lt;/strong&gt; che viene eseguito da symfony quando si salva il record.&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/LyraCatalogForm.class.php

class LyraCatalogForm extends BaseLyraCatalogForm
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at'], $this['locked_by']);
    $this-&amp;gt;widgetSchema['name']-&amp;gt;setLabel('NAME');
    $this-&amp;gt;widgetSchema['description']-&amp;gt;setLabel('DESCRIPTION');
    $this-&amp;gt;widgetSchema['is_active']-&amp;gt;setLabel('IS_ACTIVE');
    $this-&amp;gt;widgetSchema['catalog_content_types_list']-&amp;gt;setLabel('CATALOG_CONTENT_TYPES');

    $this-&amp;gt;widgetSchema['catalog_content_types_list']-&amp;gt;setOption('expanded', true);
  }
  protected function doSave($con = null)
  {
    $savingnew = $this-&amp;gt;isNew();
    parent::doSave($con);

    if ($savingnew) {
      $label = new LyraLabel();
      $label-&amp;gt;setName($this-&amp;gt;object-&amp;gt;getName());

      $label-&amp;gt;setCatalogId($this-&amp;gt;object-&amp;gt;getId());
      $label-&amp;gt;save();
      $treeObject = Doctrine::getTable('LyraLabel')-&amp;gt;getTree();
      $treeObject-&amp;gt;createRoot($label);
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Ho eseguito a questo punto il commit della &lt;strong&gt;revisione 21&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Gestione etichette&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:generate-admin backend LyraLabel --module=label&lt;/pre&gt;

&lt;p&gt;Non esiste un link nel menù principale che porti ad una lista di tutte le etichette, è possibile visualizzare le etichette di ogni catalogo dal link 'Mostra' nella colonna &lt;strong&gt;Etichette&lt;/strong&gt; della lista cataloghi.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SwnhIGgaBCI/AAAAAAAAAIw/hPCHIu4o5GE/s1600/label-backend.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 198px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SwnhIGgaBCI/AAAAAAAAAIw/hPCHIu4o5GE/s320/label-backend.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5407100356936729634" /&gt;&lt;/a&gt;
&lt;p&gt;La colonna &lt;strong&gt;Nome&lt;/strong&gt; non mostra direttamente il contenuto di un campo, ma il risultato del metodo &lt;strong&gt;getIndentName()&lt;/strong&gt; della classe &lt;strong&gt;LyraLabel&lt;/strong&gt; che fa sì che il nome venga indentato a seconda del livello che l'etichetta occupa nell'albero.&lt;/p&gt;
&lt;pre&gt;
lib/model/doctrine/LyraLabel.class.php

class LyraLabel extends BaseLyraLabel
{
  ...
  function getIndentName()
  {
    $indent = $this-&amp;gt;level-1;
    if($indent &amp;lt; 0) {
      $indent = 0;
    }
    return str_repeat('-- ', $indent).$this-&amp;gt;name;
  }
}
&lt;/pre&gt;
&lt;p&gt;La colonna &lt;strong&gt;Ordina&lt;/strong&gt; contiene due frecce per modificare l'ordine di un'etichetta rispetto alle etichette di pari livello (i nodi fratelli). In questo caso si utilizza un partial per generare il contenuto della colonna.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/label/templates/_order.php

&amp;lt;?php
$node = $lyra_label-&amp;gt;getNode();

if($node-&amp;gt;hasNextSibling()) {
  echo link_to(image_tag('backend/arrow-down.png', array('alt' =&amp;gt; __('LINK_T_MOVE_DOWN'))),'label/move?id='.$lyra_label-&amp;gt;getId().'&amp;amp;dir=0',array('title' =&amp;gt; __('LINK_T_MOVE_DOWN')));
}
if($node-&amp;gt;hasPrevSibling()) {
  echo link_to(image_tag('backend/arrow-up.png', array('alt' =&amp;gt; __('LINK_T_MOVE_UP'))),'label/move?id='.$lyra_label-&amp;gt;getId().'&amp;amp;dir=1',array('title' =&amp;gt; __('LINK_T_MOVE_UP')));
}
&lt;/pre&gt;
I link sono collegati ad un'azione &lt;strong&gt;move&lt;/strong&gt; implementata in labelActions. 
&lt;pre&gt;
apps/backend/modules/label/actions/actions.class.php

class labelActions extends autoLabelActions
{
  ...  
  public function executeMove(sfWebRequest $request)
  {
    $record = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    
    switch($request-&amp;gt;getParameter('dir')) {
      case 0:
        $next = $record-&amp;gt;getNode()-&amp;gt;getNextSibling();
        if($next) {
          $record-&amp;gt;getNode()-&amp;gt;moveAsNextSiblingOf($next);
        }
        break;
      case 1:
        $prev = $record-&amp;gt;getNode()-&amp;gt;getPrevSibling();
        if($prev) {
          $record-&amp;gt;getNode()-&amp;gt;moveAsPrevSiblingOf($prev);
        }
        break;
    }
    $this-&amp;gt;redirect('@lyra_label_label');
  }
  ...
}
&lt;/pre&gt;
Per quanto riguarda l'utilizzo dei metodi &lt;strong&gt;getNode()&lt;/strong&gt;, &lt;strong&gt;hasNextSibling()&lt;/strong&gt;, &lt;strong&gt;hasPrevSibling()&lt;/strong&gt;, &lt;strong&gt;moveAsNextSiblingOf()&lt;/strong&gt;, &lt;strong&gt;moveAsPrevSiblingOf()&lt;/strong&gt; utilizzati nel partial e nel metodo &lt;strong&gt;executeMove()&lt;/strong&gt; della classe action rimando alla documentazione ufficiale di Doctrine sui &lt;a href="http://www.doctrine-project.org/documentation/manual/1_1/en/hierarchical-data"&gt;nested set&lt;/a&gt;.

&lt;p&gt;L'unica particolarità che presenta la configurazione del form per l'inserimento e modifica di un'etichetta(classe &lt;strong&gt;LyraLabelForm&lt;/strong&gt;) è data dalla generazione della lista per la selezione dell'etichetta 'padre'. Il problema è che mentre una &lt;strong&gt;nuova&lt;/strong&gt; etichetta può essere inserita come &lt;strong&gt;figlia&lt;/strong&gt; di una qualsiasi delle etichette esistenti, quando si &lt;strong&gt;modifica&lt;/strong&gt; un'etichetta non si può selezionare come padre nessuna delle etichette sue &lt;strong&gt;discendenti&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Serve un esempio. Data una struttura di questo tipo&lt;/p&gt;

&lt;pre&gt;
PHP
-- Framework
---- Symfony
---- CakePHP
&lt;/pre&gt;
&lt;p&gt;Quando si modifica l'etichetta &lt;em&gt;PHP&lt;/em&gt; non deve essere possibile selezionare come nuovo padre né &lt;em&gt;Framework&lt;/em&gt;, nè &lt;em&gt;Symfony&lt;/em&gt;, né &lt;em&gt;CakePHP&lt;/em&gt;, perché in questo modo si distruggerebbe la struttura dell'albero. Questo controllo è fatto nel metodo &lt;strong&gt;configure()&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/LyraLabelForm.class.php

class LyraLabelForm extends BaseLyraLabelForm
{
  public function configure()
  {
    ...
    $query = Doctrine_Query::create()-&amp;gt;from('LyraLabel l');

    if($this-&amp;gt;isNew()) {
        $catalog_id = sfContext::getInstance()-&amp;gt;getUser()-&amp;gt;getAttribute('lyra_catalog_id');
        if($catalog_id) {
          $this-&amp;gt;setDefault('catalog_id', $catalog_id);
          $query-&amp;gt;where('l.catalog_id = ?', $catalog_id);
        }
    } else {
        $query-&amp;gt;where('l.catalog_id = ? AND (l.lft &amp;lt; ? OR l.rgt &amp;gt; ?)', array($this-&amp;gt;object-&amp;gt;getCatalogId(), $this-&amp;gt;object-&amp;gt;getLft(), $this-&amp;gt;object-&amp;gt;getRgt()));
    }

    $this-&amp;gt;widgetSchema['parent_id'] = new sfWidgetFormDoctrineChoice(array('model'=&amp;gt;'LyraLabel', 'order_by'=&amp;gt;array('root_id, lft', ''), 'method'=&amp;gt;'getIndentName', 'query'=&amp;gt;$query));
    $this-&amp;gt;validatorSchema['parent_id'] = new sfValidatorDoctrineChoice(array('required'=&amp;gt;false, 'model'=&amp;gt;'LyraLabel'));
    ...
  }
&lt;/pre&gt;
&lt;p&gt;In inserimento si mostra nella lista tutto l'albero delle etichette appartenenti al catalogo, in modifica si escludono i discendenti del record etichetta che stiamo modificando. Da dove viene fuori la query nell'&lt;em&gt;else&lt;/em&gt;? Per capirlo occorre conoscere più nei dettagli come viene costruito un nested set e in particolare la funzione dei campi &lt;em&gt;lft&lt;/em&gt; e &lt;em&gt;rgt&lt;/em&gt;: una spiegazione che personalmente ho trovato utile è &lt;a href="http://dev.mysql.com/tech-resources/articles/hierarchical-data.html"&gt;questa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Per riassumere la gestione etichette nel backend: con le frecce nell'elenco si modifica l'ordine tra etichette sullo stesso livello, entrando in modifica del record si può spostare un'etichetta e tutti i suoi discendenti (un ramo) sotto un'altra. Per ora è così, questa parte della gestione è un po' complessa e richiederà altro lavoro.&lt;/p&gt;

&lt;p&gt;Chi volesse giocare un po' con le nuove funzioni può allinearsi alla &lt;strong&gt;revisione 22&lt;/strong&gt; di Google Code. Ricordo che per entrare nel backend basta inserire nel browser l'indirizzo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php o http://lyra/backend.php&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questo se si è configurato un virtual host secondo le istruzioni date nei primi articoli.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6581352395802748996?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/7FJquaES-l4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6581352395802748996/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-backend-cataloghi-ed-etichette.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6581352395802748996?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6581352395802748996?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-backend-cataloghi-ed-etichette.html" title="Lyra, backend cataloghi ed etichette" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s72-c/backend-cataloghi.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CkUASX84fip7ImA9WxNbE0U.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4896691106828327948</id><published>2009-11-16T15:01:00.002+01:00</published><updated>2009-11-16T15:04:08.136+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-16T15:04:08.136+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Utilizzare symfony in Windows con WampServer</title><content type="html">&lt;p&gt;Ho pensato potesse essere utile a chi volesse seguire lo sviluppo di Lyra sotto Windows una breve guida su come configurare symfony per l'utilizzo in questo sistema operativo.&lt;/p&gt;

&lt;p&gt;Servono innanzi tutto Apache, MySql e PHP. Il modo più veloce per installarli in Windows è utilizzare un 'pacchetto' come Wamp, Xampp o EasyPHP; la procedura riportata di seguito è stata provata su &lt;a href="http://www.wampserver.com/en/"&gt;WampServer&lt;/a&gt; versione 2.0-i, per chi utilizza uno degli altri prodotti la sostanza non cambia, ma possono esserci differenze nei percorsi dei file di configurazione di Apache e dell'eseguibile PHP.&lt;/p&gt;

&lt;p&gt;Serve poi un client subversion. &lt;a href="http://tortoisesvn.tigris.org/"&gt;TortoiseSVN&lt;/a&gt; è facile da utilizzare e gratuito.&lt;/p&gt;

&lt;p&gt;Una volta installate queste componenti creiamo una cartella per l'applicazione, ad esempio &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt;.&lt;/p&gt; 

&lt;h2&gt;Checkout da repository&lt;/h2&gt;

&lt;p&gt;Come prima cosa conviene scaricare dal repository su Google Code la versione più recente dell'applicazione. In Windows Explorer fare clic con il pulsante destro sulla cartella &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt; e selezionare &lt;em&gt;SVN Checkout&lt;/em&gt; (se non c'è l'opzione non si è installato correttamente TortoiseSVN); nella finestra di dialogo inserire:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;URL of repository: http://lyra-cms.googlecode.com/svn/trunk/&lt;/li&gt;
  &lt;li&gt;Checkout directory: C:\sfprojects\lyra&lt;/li&gt;
  &lt;li&gt;Checkout depth: fully recursive (la casella 'Omit external' deve essere non selezionata)&lt;/li&gt;
  &lt;li&gt;Head revision&lt;/li&gt;
&lt;/ul&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s1600-h/checkout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 248px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s320/checkout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404493084455259442" /&gt;&lt;/a&gt;
&lt;p&gt;Il primo checkout richiede del tempo perché deve essere scaricato tutto il framework. Gli aggiornamenti a versioni successive saranno più veloci.&lt;/p&gt;


&lt;h2&gt;Configurazione virtual host&lt;/h2&gt;

&lt;p&gt;Personalmente trovo comodo lavorare in locale attraverso l'utilizzo dei virtual host: in questo modo si possono gestire molti progetti senza dover creare una miriade di sotto-cartelle nella Document Root di Apache.&lt;/p&gt;

&lt;p&gt;Assumo che l'installazione di Wamp sia stata fatta nella cartella predefinita &lt;em&gt;C:\wamp&lt;/em&gt;. Aprire con un editor di testi il file &lt;em&gt;httpd.conf&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\wamp\bin\apache\Apache2.2.11\conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Il numero di versione di Apache può essere diverso a seconda della versione di Wamp installata. Trovare questa riga&lt;/p&gt;
&lt;pre&gt;#Include conf/extra/httpd-vhosts.conf&lt;/pre&gt;

&lt;p&gt;e decommentarla rimuovendo il '#' iniziale, salvare poi il file.&lt;/p&gt;

&lt;p&gt;Aprire il file &lt;em&gt;httpd-vhosts.conf&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;code&gt;C:\wamp\bin\apache\Apache2.2.11\conf\extra&lt;/code&gt;

&lt;p&gt;Accertarsi che sia presente e attiva (cioè non preceduta da '#') la riga&lt;/p&gt;

&lt;pre&gt;NameVirtualHost *:80&lt;/pre&gt;

&lt;p&gt;Aggiungere alla fine del file&lt;/p&gt;

&lt;pre&gt;
&amp;lt;VirtualHost *:80&amp;gt;
    ServerName localhost
    DocumentRoot "c:/wamp/www/"
&amp;lt;/VirtualHost&amp;gt;

&amp;lt;VirtualHost *:80&amp;gt;  
ServerName lyra  
DocumentRoot "c:/sfprojects/lyra/web/"  
DirectoryIndex index.php
&amp;lt;Directory "c:/sfprojects/lyra/web/"&amp;gt;
    AllowOverride All
    Allow from All
&amp;lt;/Directory&amp;gt;

Alias /sf "c:/sfprojects/lyra/lib/vendor/symfony/data/web/sf"
&amp;lt;Directory "c:/sfprojects/lyra/lib/vendor/symfony/data/web/sf"&amp;gt;
 AllowOverride All
 Allow from All
&amp;lt;/Directory&amp;gt;

Alias /sfDoctrinePlugin "c:/sfprojects/lyra/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web"
&amp;lt;Directory "c:/sfprojects/lyra/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web"&amp;gt;
 AllowOverride All
 Allow from All
&amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Aprire il file &lt;em&gt;hosts&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\WINDOWS\system32\drivers\etc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Aggiungere subito dopo&lt;/p&gt;

&lt;pre&gt;127.0.0.1 localhost&lt;/pre&gt;

&lt;p&gt;questa riga&lt;/p&gt;

&lt;pre&gt;127.0.0.1 lyra&lt;/pre&gt;

&lt;p&gt;Al posto di &lt;em&gt;lyra&lt;/em&gt; si può ovviamente usare il nome che si vuole basta che corrisponda a &lt;strong&gt;ServerName&lt;/strong&gt; nella configurazione del virtual host. A questo punto bisogna eseguire Wamp, se è già in esecuzione si deve comunque riavviare Apache dal menù disponibile cliccando l'icona di notifica di Wamp.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCeva8xjNI/AAAAAAAAAIQ/ouAtL8HvlE8/s1600-h/wamp1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 202px; height: 265px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCeva8xjNI/AAAAAAAAAIQ/ouAtL8HvlE8/s320/wamp1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404494090369862866" /&gt;&lt;/a&gt;
&lt;h2&gt;Attivazione mod_rewrite&lt;/h2&gt;

&lt;p&gt;Bisogna anche attivare il modulo Apache &lt;em&gt;mod_rewrite&lt;/em&gt; per gestire URL semplificate (SEF) in symfony. Basta fare clic sull'icona di WampServer nell'area di notifica, selezionare l'opzione del menù Apache &gt;&gt; Apache modules &gt;&gt; scorrere la lista dei moduli fino a trovare &lt;em&gt;rewrite_module&lt;/em&gt; e selezionarlo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SwCuqY4j6GI/AAAAAAAAAIg/rH6yUZZsUqE/s1600-h/mod-rewrite.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 201px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SwCuqY4j6GI/AAAAAAAAAIg/rH6yUZZsUqE/s320/mod-rewrite.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404511596102019170" /&gt;&lt;/a&gt;
&lt;h2&gt;Configurazione PHP&lt;/h2&gt;

&lt;p&gt;Durante lo sviluppo di un'applicazione symfony è frequente eseguire script PHP da riga di comando. Per prima cosa copiare nella cartella &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt; il file &lt;em&gt;symfony.bat&lt;/em&gt; che si trova nella cartella &lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\sfprojects\lyra\lib\vendor\symfony\data\bin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Occorre poi configurare una variabile di ambiente &lt;strong&gt;PHP_COMMAND&lt;/strong&gt; che serve a far conoscere allo script &lt;em&gt;symfony.bat&lt;/em&gt; il percorso dell'eseguibile PHP che nel nostro caso è&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\wamp\bin\php\php5.3.0\php.exe&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Il numero di versione di PHP può essere diverso a seconda della versione di Wamp installata.&lt;/p&gt;
&lt;p&gt;In Windows XP (io a quello sono rimasto): Pannello di controllo &gt;&gt; Sistema &gt;&gt; tab Avanzate &gt;&gt; in fondo alla finestra di dialogo premere il pulsante 'Variabili di ambiente', nella finestra successiva premere 'Nuovo' sotto 'Variabili utente' (o sotto 'Variabili di sistema' se si preferisce) e inserire&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nome variabile: PHP_COMMAND&lt;/li&gt;
  &lt;li&gt;Valore variabile: C:\wamp\bin\php\php5.3.0\php.exe&lt;/li&gt;
&lt;/ul&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCegIK6dRI/AAAAAAAAAII/RTv9DetQd3k/s1600-h/variabile-sistema.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 210px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCegIK6dRI/AAAAAAAAAII/RTv9DetQd3k/s320/variabile-sistema.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404493827630855442" /&gt;&lt;/a&gt;
&lt;p&gt;Chiudere tutto con OK. Per verificare di aver fatto tutto bene aprire una finestra terminale e scrivere&lt;/p&gt;

&lt;pre&gt;
cd c:\sfprojects\lyra
symfony -V
&lt;/pre&gt;
&lt;p&gt;Deve comparire il numero della versione di symfony.&lt;/p&gt;

&lt;h2&gt;Configurazione database MySql&lt;/h2&gt;

&lt;p&gt;Da &lt;strong&gt;phpMyAdmin &lt;/strong&gt;(con Wamp si lancia navigando con il browser all'indirizzo &lt;em&gt;http://localhost/phpmyadmin/&lt;/em&gt;) creare un database ed un nuovo utente assegnandogli i privilegi sul database.&lt;/p&gt;

&lt;p&gt;Modificare il file &lt;em&gt;c:\sfprojects\lyra\config\databases.yml&lt;/em&gt; inserendo il nome database, utente e password che si sono appaena creati.&lt;/p&gt;


&lt;p&gt;Tornare alla finestra terminale, accertarsi di essere ancora in &lt;em&gt;c:\sfprojects\lyra&lt;/em&gt; e scrivere&lt;/p&gt;
&lt;pre&gt;
symfony doctrine:build-all-reload
&lt;/pre&gt;
&lt;p&gt;Dare conferma all'avviso che le tabelle del database saranno ricreate. Se non ci sono errori nella configurazione del database, si vedranno una serie di messaggi che avvisano del progresso delle varie operazioni (generazione delle classi del modello, creazione tabelle, caricamento fixtures). Alla fine scrivere&lt;/p&gt;
&lt;pre&gt;
symfony cc
&lt;/pre&gt;

&lt;p&gt;Aprendo il browser e digitando &lt;em&gt;http://lyra/frontend_dev.php&lt;/em&gt; ci si trova sulla prima pagina dell'applicazione. Per entrare nel backend l'indirizzo è &lt;em&gt;http://lyra/backend_dev.php&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;La procedura sembra complessa, ma la gran parte del lavoro (configurazione virtual host, creazione database, impostazione PHP) si fa una volta sola. Il comando &lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt; va eseguito obbligatoriamente dopo il primo checkout, in seguito quando ci si aggiorna alle successive revisioni non è necessario a meno che non vi siano state modifiche alla struttura del database. Eseguire il comando comunque non fa danno, ma va tenuto presente che le tabelle vengono cancellate, ricreate e ricaricati i dati di prova (fixtures), quindi si perdono eventuali dati inseriti.&lt;/p&gt;

&lt;p&gt;La pulizia della cache di symfony con il comando 'cc' va eseguita ogni volta dopo un checkout o update dal repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tortoise SVN&lt;/strong&gt; come ogni altro client subversion consente di creare una copia di lavoro locale aggiornata non solo alla versione più recente dell'applicazione, ma ad un qualsiasi numero di revisione che indicheremo nella finestra di dialogo dell'opzione checkout.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCe9t3zmTI/AAAAAAAAAIY/kIWdF3y-_qI/s1600-h/checkout1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 250px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCe9t3zmTI/AAAAAAAAAIY/kIWdF3y-_qI/s320/checkout1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404494335967467826" /&gt;&lt;/a&gt;
&lt;p&gt;Come detto a suo tempo, questo permette di ripercorrere l'intero sviluppo seguendo gli articoli pubblicati dall'inizio, ma anche da un qualsiasi momento successivo in quanto in ogni articolo è indicato il numero di revisione a cui si riferisce il codice.&lt;/p&gt;

&lt;p&gt;Tutta la procedura è stata testata, chi dovesse incontrare qualche problema lasci un commento e proverò ad aiutarlo.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4896691106828327948?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/tVHYTF9vtok" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4896691106828327948/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/utilizzare-symfony-in-wampserver.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4896691106828327948?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4896691106828327948?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/utilizzare-symfony-in-wampserver.html" title="Utilizzare symfony in Windows con WampServer" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s72-c/checkout.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;D0IERnc-cCp7ImA9WxNbEE4.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3181112920595286979</id><published>2009-11-12T15:15:00.000+01:00</published><updated>2009-11-12T15:18:27.958+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-12T15:18:27.958+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra,  gestione commenti backend</title><content type="html">&lt;p&gt;Iniziamo lo sviluppo della gestione commenti nel backend. Prima di tutto creiamo il modulo.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:generate-admin backend LyraComment --module=comment
&lt;/pre&gt;

&lt;p&gt;Poi passiamo alla personalizzazione del file &lt;em&gt;generator.yml&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
config:
      ...
      list:
        title: TITLE_COMMENTS
        display: [comment_article,author_name,content,_published,created_at]
        fields:
          comment_article: {label: TH_ARTICLE_TITLE}
          author_name: {label: TH_AUTHOR_NAME}
          author_email: {label: TH_AUTHOR_EMAIL}
          author_url: {label: TH_AUTHOR_URL}
          content: {label: TH_CONTENT}
          created_at: {label: TH_CREATED_AT, date_format: dd.MM.yy hh:ss}
          updated_at: {label: TH_UPDATED_AT, date_format: dd.MM.yy hh:ss}
        batch_actions:
          _delete: ~
          publish: {label: PUBLISH}
          unpublish: {label: UNPUBLISH}
        table_method: getBackendItemsQuery
...
&lt;/pre&gt;
&lt;p&gt;Vediamo i vari 'blocchi' uno ad uno.&lt;/p&gt;
&lt;h2&gt;Elenco commenti&lt;/h2&gt;
&lt;p&gt;In &lt;strong&gt;list&lt;/strong&gt; definiamo la configurazione delle lista commenti: impostiamo il titolo della pagina (&lt;strong&gt;title&lt;/strong&gt;) in una forma che dovrà essere tradotta nei file di lingua, l'elenco delle colonne (&lt;strong&gt;display&lt;/strong&gt;) con le relative intestazioni e formato (&lt;strong&gt;fields&lt;/strong&gt;) e le azioni batch &lt;strong&gt;delete&lt;/strong&gt; (predefinita), &lt;strong&gt;publish&lt;/strong&gt; e &lt;strong&gt;unpublish&lt;/strong&gt; che dovremo implementare in modo del tutto analogo a quanto fatto per la gestione articoli (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/11/symfony-personalizzare-il-backend.html"&gt;simfony, personalizzare il backend&lt;/a&gt;).&lt;/p&gt;
 
&lt;p&gt;Da notare la presenza di &lt;em&gt;comment_article&lt;/em&gt; nella lista dei campi da mostrare nelle colonne dell'elenco: non è un campo della tabella Commenti, ma l'identificatore di una relazione impostata nello schema, solo che nel file di configurazione si separano le parole con il carattere sottolineato, nella definizione dello schema si utilizza il 'camel case'.&lt;/p&gt;

&lt;pre&gt;
config/doctrine/schema.yml

LyraComment:
 ...
  relations:
    CommentArticle:
      class: LyraArticle
      local: article_id
      foreign: id
      foreignAlias: ArticleComments
      onDelete: CASCADE
&lt;/pre&gt;

&lt;p&gt;In questo modo possiamo avere nell'elenco il titolo dell'articolo invece dell'ID.&lt;/p&gt;

&lt;p&gt;La colonna &lt;strong&gt;_published&lt;/strong&gt; mostra il contenuto di un &lt;strong&gt;partial&lt;/strong&gt;. Serve a consentire la modifica dello stato pubblicato non pubblicato del commento restando nella visualizzazione elenco. La spiegazione nei dettagli è nell'articolo precedente perché il funzionamento è identico a quello della stessa colonna della gestione articoli: le azioni &lt;strong&gt;publish&lt;/strong&gt; ed &lt;strong&gt;unpublish&lt;/strong&gt;, collegate alle icone nel partial (&lt;em&gt;apps/backend/modules/comment/templates/_published.php&lt;/em&gt;), sono gestite come di regola dalla classe &lt;strong&gt;commentActions&lt;/strong&gt;, il metodo &lt;strong&gt;publish()&lt;/strong&gt; è implementato in &lt;strong&gt;LyraComment&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;Azioni batch&lt;/h3&gt;

&lt;p&gt;Anche per quanto riguarda questa parte posso tranquillamente rimandare all'articolo precedente visto che il funzionamento delle azioni batch &lt;strong&gt;publish&lt;/strong&gt; e &lt;strong&gt;unpublish&lt;/strong&gt; è identico a quello della gestione articoli.&lt;/p&gt;

&lt;h3&gt;Query creazione lista&lt;/h3&gt;

&lt;p&gt;Quando in una lista che mostra i record di una tabella il contenuto di una colonna viene ottenuto da una tabella correlata, come nel nostro caso il titolo dell'articolo dalla tabella articoli, conviene modificare la query utilizzata per l'estrazione dei record ed aggiungere una JOIN tra le tabelle. In questo modo si evita l'esecuzione di una query SQL distinta per ogni riga della lista.&lt;/p&gt;

&lt;p&gt;La chiave &lt;strong&gt;table_method&lt;/strong&gt; indica il nome del metodo che ritorna la query da eseguire e che viene implementato nel modello.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraCommentTable.class.php

class LyraCommentTable extends Doctrine_Table
{
  ...
  public function getBackendItemsQuery(Doctrine_Query $q)
  {
    $rootAlias = $q-&amp;gt;getRootAlias();
    $q-&amp;gt;leftJoin($rootAlias . '.CommentArticle a');

    return $q;
  }
}
&lt;/pre&gt;

&lt;h2&gt;Filtri commenti&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
    config:
      ...
      list:
      ...
      filter:
        display: [article_id, is_active]
      ...
&lt;/pre&gt;
&lt;p&gt;In &lt;strong&gt;filter&lt;/strong&gt; impostiamo i filtri di ricerca che ci consentono di visualizzare nell'elenco  i commenti pubblicati o non pubblicati (&lt;strong&gt;is_active&lt;/strong&gt;) e tutti i commenti di un determinato articolo (&lt;strong&gt;article_id&lt;/strong&gt;).&lt;/p&gt;

&lt;h2&gt;Modifica commento&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
    config:
     ...
      list:
       ...
      filter:
       ...
      form:
        class: BackendLyraCommentForm
       ...
&lt;/pre&gt;
&lt;p&gt;Come si è visto la pubblicazione di un commento può essere fatta rimanendo nella visualizzazione elenco, se invece serve modificare il testo o uno degli altri campi si utilizza la funzione modifica standard.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;form&lt;/strong&gt; specifichiamo la &lt;strong&gt;classe&lt;/strong&gt; da utilizzare per il modulo di inserimento e modifica commenti del backend. La classe deriva da quella creata per il frontend a sua volta derivata dalla classe base generata da Doctrine.&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/BackendLyraCommentForm.class.php

class BackendLyraCommentForm extends LyraCommentForm
{
  public function configure()
  {
      parent::configure();
      $this-&amp;gt;widgetSchema['is_active']-&amp;gt;setLabel('IS_ACTIVE');

      $this-&amp;gt;widgetSchema-&amp;gt;setHelp('author_email',false);
      $this-&amp;gt;widgetSchema-&amp;gt;moveField('is_active', sfWidgetFormSchema::FIRST);

  }
  protected function removeFields()
  {
      unset($this['created_at'], $this['updated_at']);
  }
}
&lt;/pre&gt;

&lt;p&gt;Nel metodo &lt;strong&gt;configure()&lt;/strong&gt;, prima richiamiamo la configurazione della classe base e poi impostiamo le configurazioni specifiche per il backend. Sovrascrivendo il metodo &lt;strong&gt;removeFields()&lt;/strong&gt; possiamo rendere visibili nel form di backend campi che erano stati resi invisibili nel form di frontend, in particolare il campo &lt;strong&gt;is_active&lt;/strong&gt; che contiene lo stato pubblicato / non pubblicato del commento.&lt;/p&gt;

&lt;p&gt;Resta da sistemare il link che porta dall'elenco articoli alla lista dei commenti inseriti su un articolo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s1600-h/lyra-articles.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 288px; height: 140px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s320/lyra-articles.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402890824320174034" /&gt;&lt;/a&gt;
&lt;pre&gt;
apps/backend/modules/article/templates/_comments.php

&amp;lt;?php
$total = $lyra_article-&amp;gt;countComments();
echo $total . ' / ' . ($total - $lyra_article-&amp;gt;countActiveComments());
?&amp;gt;
 (&amp;lt;?php echo link_to(__('LINK_SHOW_COMMENTS'),'@lyra_comment_comment?id=' . $lyra_article-&amp;gt;getId()); ?&amp;gt;)
&lt;/pre&gt;
&lt;p&gt;Si crea il link in base alla rotta che porta alla lista commenti (azione &lt;strong&gt;index&lt;/strong&gt;) aggiungendo un parametro &lt;em&gt;id&lt;/em&gt; (articolo). Poi nella classe actions del modulo commenti si legge il parametro per impostare automaticamente un filtro che visualizzi i commenti dell'articolo con quel determinato ID.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/comment/actions/actions.class.php

class commentActions extends autoCommentActions
{
  ...
  public function executeIndex(sfWebRequest $request)
  {
    if($request-&amp;gt;getParameter('id')) {
      $this-&amp;gt;setFilters(array('article_id'=&amp;gt;$request-&amp;gt;getParameter('id')));
    }
    parent::executeIndex($request);
    
  }
}
&lt;/pre&gt; 
&lt;p&gt;Impostato il filtro, si passa il controllo all'azione della classe base che è poi quella creata dal generatore del backend.&lt;/p&gt;

&lt;p&gt;Ho infine corretto un errore nella classe actions del modulo article del frontend: dopo la modifica delle rotte degli articoli (in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html"&gt;Generare URL SEF in symfony&lt;/a&gt;) il reindirizzamento alla pagina dell'articolo dopo aver inserito un commento non funzionava più. Tutto questo si trova nella &lt;strong&gt;revisione 20&lt;/strong&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3181112920595286979?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/GOT_8l9U-6o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3181112920595286979/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-commenti-backend.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3181112920595286979?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3181112920595286979?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-commenti-backend.html" title="Lyra,  gestione commenti backend" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s72-c/lyra-articles.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DEIGQn08eSp7ImA9WxNUF0o.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-423461604844258652</id><published>2009-11-09T15:20:00.000+01:00</published><updated>2009-11-09T15:22:03.371+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-09T15:22:03.371+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, personalizzare il backend</title><content type="html">&lt;p&gt;Arrivati a questo punto è necessario iniziare lo sviluppo delle funzionalità di backend di Lyra. Sarà anche l'occasione per una veloce panoramica sul &lt;strong&gt;generatore di backend&lt;/strong&gt; di symfony. Chi non ha grande familiarità con il framework può trovare molti maggiori dettagli nel tutorial ufficiale (&lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/12"&gt;Admin Generator&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Per prima cosa creiamo l'applicazione con il comando&lt;/p&gt;

&lt;pre&gt;
./symfony generate:app --escaping-strategy=on --csrf-secret=xgt67jhbv backend
&lt;/pre&gt;

&lt;p&gt;Si utilizzano le stesse opzioni viste per la generazione dell'applicazione frontend (in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html"&gt;symfony, creazione progetto e applicazione&lt;/a&gt;): &lt;strong&gt;escaping-strategy&lt;/strong&gt; impostato a &lt;em&gt;on&lt;/em&gt; e &lt;strong&gt;csrf-secret&lt;/strong&gt; con una sequenza di caratteri scelti a caso.&lt;/p&gt;

&lt;p&gt;Eseguito il comando si noterà che è stata creata una cartella &lt;em&gt;apps/backend&lt;/em&gt; e nella cartella &lt;em&gt;web&lt;/em&gt; i front controller per l'applicazione backend:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;backend.php&lt;/li&gt;
&lt;li&gt;backend-dev.php&lt;/li&gt;&lt;/ul&gt;

&lt;h2&gt;Backend articoli&lt;/h2&gt;
 
&lt;p&gt;Symfony consente di generare automaticamente un'interfaccia di backend per ciascuna classe del modello. Iniziamo con la gestione articoli.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:generate-admin backend LyraArticle --module=article
&lt;/pre&gt;

&lt;p&gt;A questo punto inserendo nel browser l'indirizzo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php/article&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;siamo già in grado di visualizzare l'elenco degli articoli inseriti. Questo dando per scontato che si siano seguite le istruzioni degli articoli precedenti per creare un virtual host e il database del progetto e si siano eseguiti i comandi per la generazione del modello, delle tabelle e l'inserimento dei dati di esempio.&lt;/p&gt;

&lt;p&gt;Anche se l'interfaccia è funzionante e già consente di inserire, modificare e cancellare gli articoli, l'aspetto della pagina è piuttosto brutto da vedere perché non abbiamo personalizzato il &lt;strong&gt;layout&lt;/strong&gt; del backend e l'elenco degli articoli risulta troppo largo in quanto è stata creata una colonna per ciascun campo della tabella.&lt;/p&gt;

&lt;p&gt;La prima cosa da fare è la modifica del layout (&lt;em&gt;apps/backend/templates/layout.php&lt;/em&gt;) con l'aggiunta del relativo foglio di stile (&lt;em&gt;web/css/admin.css&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;Bisogna anche modificare questa riga di &lt;em&gt;apps/backend/config/view.yml&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
stylesheets:    [main.css]
&lt;/pre&gt;
&lt;p&gt;in questo modo&lt;/p&gt;
&lt;pre&gt;
stylesheets:    []
&lt;/pre&gt;
&lt;p&gt;Questo perché utilizziamo la funzione helper &lt;strong&gt;use_stylesheet()&lt;/strong&gt; per richiamare il foglio di stile &lt;em&gt;admin.css&lt;/em&gt; nel layout del backend.&lt;/p&gt;

&lt;p&gt;Il layout è ancora abbastanza grezzo, ma ritengo che in questa fase sia più importante mettere in piedi qualcosa che funzioni e pensare alle rifiniture in un secondo tempo. Per cui non riporto neppure il codice che dovrà necessariamente essere modificato parecchio nel corso dello sviluppo. Il contenuto dei due file può comunque essere visualizzato su Google Code (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/backend/templates/layout.php"&gt;layout.php&lt;/a&gt;, &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/web/css/admin.css"&gt;admin.css&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Un primo livello di personalizzazione del backend si effettua modificando le impostazioni nel file di configurazione &lt;em&gt;generator.yml&lt;/em&gt; nella cartella &lt;em&gt;config&lt;/em&gt; di ogni modulo. La procedura è abbastanza standardizzata e ben documentata, mi limiterò ad elencare in estrema sintesi le operazioni svolte.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/config/generator.yml
...
  config:
      actions: ~
      fields:  ~
      list:
        title: TITLE_ARTICLES
        display: [=title,_comments,_published,_front_page,created_at,id]
        fields:
          title: {label: TH_TITLE}
          created_at: {label: TH_CREATED_AT, date_format: dd.MM.yy hh:mm}
          updated_at: {label: TH_UPDATED_AT, date_format: dd.MM.yy hh:mm}
          id: {label: TH_ID}
        ...
      filter:
        display: [title,is_active]
...
&lt;/pre&gt;
&lt;p&gt;Viene impostato un titolo per la pagina, vengono scelti i campi da mostrare nell'elenco in modo da ridurre il numero delle colonne e modificate le etichette delle intestazioni colonna. Il testo effettivo per titolo ed etichette come al solito sarà incluso nei file delle traduzioni. &lt;/p&gt;

&lt;p&gt;Vengono scelti i filtri di ricerca. Al momento si consente una ricerca per titolo articolo e stato pubblicazione.&lt;/p&gt;

&lt;p&gt;Da notare le colonne &lt;strong&gt;_comments&lt;/strong&gt;, &lt;strong&gt;_published&lt;/strong&gt;, &lt;strong&gt;_front_page&lt;/strong&gt; impostate nel parametro &lt;strong&gt;display&lt;/strong&gt;. il carattere '_'  iniziale ci dice che il contenuto di queste colonne non è il valore di un campo, ma il risultato della elaborazione di altrettanti &lt;strong&gt;partial&lt;/strong&gt; definiti in &lt;em&gt;apps/backend/modules/article/templates&lt;/em&gt;. Vediamoli uno per uno.&lt;/p&gt;

&lt;h2&gt;Contatori numero commenti articolo&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/article/templates/_comments.php

&amp;lt;?php
$total = $lyra_article-&amp;gt;countComments();
echo $total . ' / ' . ($total - $lyra_article-&amp;gt;countActiveComments());
?&amp;gt;
 (&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;&amp;lt;?php echo __('LINK_SHOW_COMMENTS') ?&amp;gt;&amp;lt;/a&amp;gt;)
&lt;/pre&gt;
&lt;p&gt;Dovrebbe essere abbastanza comprensibile: nella colonna vengono visualizzati il numero totale dei commenti dell'articolo seguito dal numero dei commenti in attesa di moderazione e da un link che porterà all'elenco dei commenti dell'articolo. Il link non funzionerà fino a che non avremo sviluppato il modulo commenti nel backend. I metodi &lt;strong&gt;countComments()&lt;/strong&gt; e &lt;strong&gt;countActiveComments()&lt;/strong&gt; sono implementati nel modello (classe &lt;strong&gt;LyraArticle&lt;/strong&gt;) e utilizzano due semplici query per calcolare i valori dei contatori. &lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
  ...
  public function countActiveComments()
  {
    return $this-&amp;gt;getActiveCommentsQuery()
      -&amp;gt;count();
  }
  public function countComments()
  {
    return $this-&amp;gt;getCommentsQuery()
      -&amp;gt;count();
  }
  ...
  protected function getCommentsQuery()
  {
    $q = Doctrine_Query::create()
      -&amp;gt; from('LyraComment c')
      -&amp;gt;andWhere('c.article_id = ?', $this-&amp;gt;getId());
    return $q;
  }
  protected function getActiveCommentsQuery()
  {
    $q = Doctrine::getTable('LyraComment')
      -&amp;gt;getActiveItemsQuery();

    $q-&amp;gt;andWhere($q-&amp;gt;getRootAlias() .'.article_id = ?', $this-&amp;gt;getId());
      
    return $q;
  }
} //fine LyraArticle
&lt;/pre&gt;

&lt;h2&gt;Stato pubblicato / non pubblicato&lt;/h2&gt;

&lt;p&gt;Nel backend standard di symfony nelle colonne relative ai campi &lt;strong&gt;boolean&lt;/strong&gt; viene mostrato un segno di spunta se il campo è &lt;em&gt;true&lt;/em&gt;, se è &lt;em&gt;false&lt;/em&gt; la cella viene lasciata vuota. Per modificare il valore del campo da vero a falso e viceversa si deve entrare in modifica del record. Sono abituato al backend di Joomla dove in questi casi il cambio di stato (ad esempio pubblicato / non pubblicato) si può effettuare rimanendo in visualizzazione elenco semplicemente facendo click sull'icona nella cella e ho voluto mantenere questa scorciatoia. La cosa si può fare abbastanza semplicemente mostrando un partial (&lt;strong&gt;_published&lt;/strong&gt;) al posto del valore del campo.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/templates/_published.php

&amp;lt;?php if ($lyra_article-&amp;gt;getIsActive()): ?&amp;gt;
  &amp;lt;?php echo link_to(image_tag('backend/yes.png', array('alt' =&amp;gt; __('LINK_T_PUBLISHED'))),'article/unpublish?id='.$lyra_article-&amp;gt;getId(), array('title' =&amp;gt; __('LINK_T_PUBLISHED'))) ?&amp;gt;
&amp;lt;?php else: ?&amp;gt;
  &amp;lt;?php echo link_to(image_tag('backend/no.png', array('alt' =&amp;gt; __('LINK_T_UNPUBLISHED', array(), 'sf_admin'))),'article/publish?id='.$lyra_article-&amp;gt;getId(), array('title' =&amp;gt; __('LINK_T_UNPUBLISHED'))) ?&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Se l'articolo è pubblicato si mostra l'icona del segno di spunta collegata all'azione per de-pubblicare ) l'articolo; l'inverso se l'articolo non è pubblicato.&lt;/p&gt;

&lt;p&gt;Aggiungiamo la gestione delle azioni &lt;strong&gt;publish&lt;/strong&gt; / &lt;strong&gt;unpublish&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/article/actions/actions.class.php
..
class articleActions extends autoArticleActions
{
  public function executePublish(sfwebRequest $request)
  {
    $this-&amp;gt;lyra_article = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;lyra_article-&amp;gt;publish();
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_PUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  public function executeUnpublish(sfwebRequest $request)
  {
    $this-&amp;gt;lyra_article = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;lyra_article-&amp;gt;publish(false);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_UNPUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  ...
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Il metodo publish() va implementato nel modello&lt;/p&gt;
&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
  ...
  public function publish($on = true)
  {
    $this-&amp;gt;setIsActive($on);
    $this-&amp;gt;save();
  }
  ...
} //fine LyraArticle
&lt;/pre&gt;
&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgYWRbfV7I/AAAAAAAAAHo/Uxqkrr28RLE/s1600-h/lyra-backend2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 101px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgYWRbfV7I/AAAAAAAAAHo/Uxqkrr28RLE/s320/lyra-backend2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402094523945801650" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Stato in prima pagina / non in prima pagina&lt;/h2&gt;

&lt;p&gt;La stessa cosa viene fatta per lo stato di pubblicazione in prima pagina. Non riporto il codice che è praticamente identico: il partial è &lt;strong&gt;_front_page&lt;/strong&gt; e le azioni &lt;strong&gt;feature&lt;/strong&gt; e &lt;strong&gt;unfeature&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Azioni batch&lt;/h2&gt;

&lt;p&gt;Tramite &lt;strong&gt;azioni batch&lt;/strong&gt; si può compiere una determinata operazione 'in serie' su un gruppo di record selezionati nell'elenco. Il generatore di backend di symfony crea automaticamente un'azione di questo tipo che consente la cancellazione di record multipli.&lt;/p&gt;

&lt;p&gt;Ne creeremo altre due personalizzate per impostare / rimuovere lo stato di pubblicato su più articoli. Per prima cosa definiamo le azioni batch nel file di configurazione.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/config/generator.yml
...
    config:
      actions: ~
      fields:  ~
      list:
        #parte già esaminata sopra
        batch_actions:
          _delete: ~
          publish: {label: PUBLISH}
          unpublish: {label: UNPUBLISH}
      ...
&lt;/pre&gt;
&lt;p&gt;Scriviamo poi i gestori delle azioni nella classe actions del modulo article.&lt;/p&gt;

&lt;pre&gt;
class articleActions extends autoArticleActions
{
 
  public function executeBatchPublish(sfWebRequest $request)
  {
    $ids = $request-&amp;gt;getParameter('ids');
    Doctrine::getTable('LyraArticle')-&amp;gt;publish($ids);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_PUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  public function executeBatchUnpublish(sfWebRequest $request)
  {
    $ids = $request-&amp;gt;getParameter('ids');
    Doctrine::getTable('LyraArticle')-&amp;gt;publish($ids, false);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_UNPUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;I nomi dei metodi che processano un'azione batch devono seguire uno standard: executeBatch + nome azione. Il parametro &lt;em&gt;ids&lt;/em&gt; della richiesta contiene un array con gli &lt;strong&gt;ID&lt;/strong&gt; dei record selezionati tramite le caselle di selezione nell'elenco. &lt;/p&gt;

&lt;p&gt;Implementiamo il metodo &lt;strong&gt;publish()&lt;/strong&gt; in &lt;strong&gt;LyraArticleTable&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;
class LyraArticleTable extends Doctrine_Table
{
  public function publish($ids, $on = true)
  {
    $q = $this-&amp;gt;createQuery('a')
      -&amp;gt;whereIn('a.id', $ids);

    foreach ($q-&amp;gt;execute() as $item) {
      $item-&amp;gt;publish($on);
    }
  }
} //fine LyraArticleTable
&lt;/pre&gt;
&lt;p&gt;Viene fatta una query per selezionare i record in base al valore degli ID passati dall'azione. Per ogni record si invoca il metodo &lt;strong&gt;publish()&lt;/strong&gt; di &lt;strong&gt;LyraArticle&lt;/strong&gt; che abbiamo creato poco sopra.&lt;/p&gt;
&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgZL2600xI/AAAAAAAAAHw/GoQ5T99BxT0/s1600-h/lyra-backend3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 279px; height: 245px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgZL2600xI/AAAAAAAAAHw/GoQ5T99BxT0/s320/lyra-backend3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402095444542411538" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Tema di backend personalizzato&lt;/h2&gt;

&lt;p&gt;Per ottenere un livello di personalizzazione maggiore di quelo che si ottiene attraverso le impostazioni del file di configurazione, ci si può creare un proprio tema per il backend da utilizzare al posto di quello predefinito.&lt;/p&gt;

&lt;p&gt;La procedura è spiegata nella documentazione ufficiale (&lt;a href="http://www.symfony-project.org/book/1_2/14-Generators"&gt;Admin Generator&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;La cosa più semplice da fare è partire dal tema per il backend predefinito che si trova in&lt;/p&gt;

&lt;pre&gt;
lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin
&lt;/pre&gt;

&lt;p&gt;Copiare il contenuto della cartella &lt;em&gt;admin&lt;/em&gt; (cioè le cartelle &lt;em&gt;parts&lt;/em&gt;, &lt;em&gt;skeleton&lt;/em&gt; e &lt;em&gt;template&lt;/em&gt;) in &lt;em&gt;data/generator/sfDoctrineModule/admin&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Possiamo a questo punto personalizzare il tema in questa cartella senza rischiare di perdere le nostre modifiche al successivo aggiornamento del framework. Al momento le personalizzazioni sono minime, ho soltanto creato uno slot per visualizzare il titolo delle pagine in una posizione diversa.&lt;/p&gt;

&lt;p&gt;Da così&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgXXOmDsyI/AAAAAAAAAHY/40VfeDX-eIg/s1600-h/lyra-backend.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 130px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgXXOmDsyI/AAAAAAAAAHY/40VfeDX-eIg/s320/lyra-backend.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402093440853062434" /&gt;&lt;/a&gt;
&lt;p&gt;a così&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgXfmUmrpI/AAAAAAAAAHg/BDg-L4baI9w/s1600-h/lyra-backend1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 102px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgXfmUmrpI/AAAAAAAAAHg/BDg-L4baI9w/s320/lyra-backend1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402093584661261970" /&gt;&lt;/a&gt;
&lt;pre&gt;
data/generator/sfDoctrineModule/admin/template/templates/indexSuccess.php
...
[?php slot('page_title',&amp;lt;?php echo $this-&amp;gt;getI18NString('list.title') ?&amp;gt;) ?]
&amp;lt;div id=&amp;quot;sf_admin_container&amp;quot;&amp;gt;
//La riga seguente viene rimossa
&lt;s&gt;&amp;lt;h1&amp;gt;[?php echo &amp;lt;?php echo $this-&amp;gt;getI18NString('list.title') ?&amp;gt; ?]&amp;lt;/h1&amp;gt;&lt;/s&gt;
...
&lt;/pre&gt; 

Lo slot è richiamato in questa parte del layout

&lt;pre&gt;
apps/backend/templates/layout.php
...
      &amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
        &amp;lt;h1&amp;gt;
          &amp;lt;?php include_slot('page_title') ?&amp;gt;
        &amp;lt;/h1&amp;gt;
      &amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;Modifiche del tutto analoghe sono state fatte nei file &lt;strong&gt;editSuccess.php&lt;/strong&gt; e &lt;strong&gt;newSucces.php&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le personalizzazioni apportate al tema in questo modo saranno applicate anche ai moduli creati successivamente con il comando  &lt;strong&gt;doctrine:generate-admin&lt;/strong&gt;. Lo vedremo la prossima volta quando sarà creato il modulo per la gestione dei commenti da backend.&lt;/p&gt;

&lt;p&gt;Il codice della parte discussa fino a questo momento è come al solito su Google Code (&lt;strong&gt;revisione 19&lt;/strong&gt;).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-423461604844258652?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/1ZhdyGFYaM4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/423461604844258652/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/symfony-personalizzare-il-backend.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/423461604844258652?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/423461604844258652?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/symfony-personalizzare-il-backend.html" title="symfony, personalizzare il backend" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgYWRbfV7I/AAAAAAAAAHo/Uxqkrr28RLE/s72-c/lyra-backend2.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DEcGRXo9eip7ImA9WxNUFEw.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8144363429249550121</id><published>2009-11-05T11:12:00.000+01:00</published><updated>2009-11-05T11:13:44.462+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-05T11:13:44.462+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Generare URL SEF in symfony</title><content type="html">&lt;p&gt;Quando si sono delineate le funzionalità essenziali di Lyra, nell'elenco era presente la creazione di URL semplificate, 'pulite' o SEF (ognuno utilizzi la terminologia che preferisce). Fino a questo momento abbiamo incontrato tre tipi di URL.&lt;/p&gt;

&lt;p&gt;1) Prima pagina e questa ovviamente non pone problemi particolari.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2) Vista articolo a tutta pagina. Esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/show/id/2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3) Lista articoli per etichetta. Esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/label/id/1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Queste URL non sono troppo lunghe e non presentano una lista di parametri sotto forma di query string, sono sicuramente migliori di, ad esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/index.php?module=article&amp;action=show&amp;id=2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Però credo che molti utenti  avrebbero come minimo qualche dubbio a definirle vere e proprie URL SEF. Va detto che hanno il vantaggio di poter essere generate in base a &lt;strong&gt;rotte predefinite&lt;/strong&gt; senza che sia necessario modificare alcun file di configurazione, se si è disposti a rinunciare a questo vantaggio si possono personalizzare le URL della propria applicazione come si preferisce.&lt;/p&gt;

&lt;p&gt;Come esempio modificheremo la URL della vista articolo a tutta pagina in modo che risulti così:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/titolo-articolo.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Per prima cosa definiamo una nuova rotta. L'ordine delle definizioni è importante per cui inseriamo la nostra prima di quelle già presenti (rotte di default)&lt;/p&gt;

&lt;pre&gt;
apps/frontend/config/routing.yml

article_show:
  url: /article/:slug.html
  class: sfDoctrineRoute
  options:
    type: object
    model: LyraArticle
    method: findItem
  param:
    module: article
    action: show
  requirements:
    sf_method: [get]
&lt;/pre&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;url&lt;/strong&gt; indica il formato della URL generata dalla rotta: gli identificatori preceduti da ':' (es. &lt;strong&gt;:slug&lt;/strong&gt;) sono variabili;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;class&lt;/strong&gt; è la classe della rotta: &lt;strong&gt;sfDoctrineRoute&lt;/strong&gt; indica che questa rotta è associata ad un oggetto (record) o insieme di oggetti  (collezione di record) istanze di una classe modello Doctrine;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;options&lt;/strong&gt; indica che la rotta è associata ad un singolo record (&lt;strong&gt;type&lt;/strong&gt; è &lt;em&gt;object&lt;/em&gt;, l'alternativa, &lt;em&gt;list&lt;/em&gt; indicherebbe l'associazione con una collezione di record) di classe &lt;strong&gt;LyraArticle&lt;/strong&gt;. &lt;strong&gt;findItem&lt;/strong&gt; è il metodo del modello che servirà a reperire l'oggetto corrispondente alla rotta, nel nostro caso il record articolo da visualizzare;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;param&lt;/strong&gt; indica che la richiesta che corrisponde a questa rotta verrà processata dall'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt;;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;requirements&lt;/strong&gt; indica che il metodo della richiesta deve essere &lt;strong&gt;GET&lt;/strong&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Bisogna poi modificare i template in modo che i link agli articoli siano generati in base alla rotta appena definita.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/_list.php

...
  &amp;lt;h2 class=&amp;quot;article-title&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to($item-&amp;gt;getTitle(), '@article_show?slug=' . $item-&amp;gt;getSlug())?&amp;gt;
  &amp;lt;/h2&amp;gt;
...
  &amp;lt;span class=&amp;quot;article-readmore&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to(__('LINK_READMORE'), '@article_show?slug=' . $item-&amp;gt;getSlug(), array('title'=&amp;gt;$item-&amp;gt;getTitle()))?&amp;gt;
  &amp;lt;/span&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;Alla funzione helper &lt;strong&gt;link_to()&lt;/strong&gt; passiamo direttamente il nome della rotta preceduto da '@' a cui vengono accodati i valori per le parti variabili della rotta (&lt;strong&gt;:slug&lt;/strong&gt;) nella stessa forma che sarebbe utilizzata per costruire una query string. Chiaramente il link risultante viene generato come indicato dal parametro &lt;strong&gt;url&lt;/strong&gt; nella definizione della rotta, cioè in un formato a 'segmenti' senza alcun parametro in forma di query string.&lt;/p&gt;

&lt;p&gt;Resta da vedere come viene individuato l'articolo da visualizzare quando viene cliccato uno dei link generati nel modo appena visto. Abbiamo detto che una rotta è associata ad un oggetto (o ad una collezione di oggetti), quindi partendo dalla rotta possiamo risalire all'oggetto corrispondente. Questo avviene nell'azione (le righe sbarrate sono quelle presenti nella versione precedente e ora rimosse)&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {
    &lt;s&gt;$this-&amp;gt;item = Doctrine::getTable('LyraArticle')&lt;/s&gt;
    &lt;s&gt;  -&amp;gt;find($request-&amp;gt;getParameter('id'));&lt;/s&gt;
    &lt;s&gt;$this-&amp;gt;forward404Unless($this-&amp;gt;item);&lt;/s&gt;
    $this-&amp;gt;item = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    ...
   }
 ...  
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Il metodo &lt;strong&gt;getObject()&lt;/strong&gt; invoca il metodo definito nelle &lt;strong&gt;options&lt;/strong&gt; della rotta (&lt;strong&gt;findItem()&lt;/strong&gt;) e gli passa un array di parametri contenente le variabili della rotta. Nel metodo dobbiamo eseguire una query in base a questi parametri e ritornare l'oggetto oppure &lt;em&gt;false&lt;/em&gt;: nel primo caso l'oggetto viene passato indietro all'azione ed il flusso continua; nel secondo caso nel codice di &lt;strong&gt;getObject()&lt;/strong&gt; viene sollevata un'eccezione che genera un errore 404.&lt;/p&gt;

&lt;p&gt;Proprio perché nel caso di oggetto non trovato l'errore viene gestito internamente abbiamo rimosso dall'azione la riga&lt;/p&gt;

&lt;pre&gt;
$this-&gt;forward404Unless($this-&gt;item);
&lt;/pre&gt;

&lt;p&gt;Se per qualsiasi ragione vogliamo continuare a gestire questa situazione di errore nel codice dell'azione, basta modificare la definizione della rotta in questo modo&lt;/p&gt;

&lt;pre&gt;
article_show:
  ...
  options:
    model: LyraArticle
    type: object
    method: findItem
    allow_empty: true
...
&lt;/pre&gt;
&lt;p&gt;Con l'opzione &lt;strong&gt;allow_empty&lt;/strong&gt; impostata a &lt;em&gt;true&lt;/em&gt; il metodo &lt;strong&gt;getObject()&lt;/strong&gt; ritorna &lt;em&gt;null&lt;/em&gt; in caso di oggetto non trovato senza generare internamente nessuna eccezione. Chiaramente a questo punto dovremmo reintrodurre la chiamata al metodo &lt;strong&gt;forward404Unless()&lt;/strong&gt; o comunque gestire la situazione opportunamente. Il codice ad esempio dovrebbe essere modificato così:&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {    
    $this-&amp;gt;item = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;forward404Unless($this-&amp;gt;item);
    ...
   }
 ...  
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Resta solo da implementare &lt;strong&gt;findItem()&lt;/strong&gt; nel modello.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraArticleTable.class.php

class LyraArticleTable extends Doctrine_Table
{
...
  public function findItem($params = array())
  {
    if(!isset($params['slug'])) {
      return false;
    }
    $q = $this-&amp;gt;getActiveItemsQuery();
    $q-&amp;gt;andWhere($q-&amp;gt;getRootAlias() .'.slug = ?', $params['slug']);
    
    return $q-&amp;gt;fetchOne();
  }
} // fine LyraArticleTable
&lt;/pre&gt;
&lt;p&gt;Nel nostro esempio l'array di parametri contiene un solo elemento, il valore del campo &lt;strong&gt;slug&lt;/strong&gt; dell'articolo.&lt;/p&gt;

&lt;p&gt;Ho inoltre aggiunto una rotta (&lt;strong&gt;article_label&lt;/strong&gt;) per dare questo formato alle URL della pagina con l'elenco degli articoli per etichetta generate dal componente visto nei due articoli precedenti:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/label/slug-etichetta&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Il procedimento è del tutto analogo a quello appena visto, il codice è disponibile nel repository (&lt;strong&gt;revisione 18&lt;/strong&gt;) insieme a tutto quello illustrato sopra.&lt;/p&gt;

&lt;p&gt;Naturalmente le possibilità offerte da symfony non si fermano certo qui. Si potrebbe includere la data dell'articolo nella URL per creare un &lt;em&gt;permalink&lt;/em&gt; in stile blog&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/2009/11/titolo-articolo.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Oppure includere le etichette/categorie&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/javascript/jquery/articolo-su-jquery.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ma è prematuro continuare lo sviluppo di questa parte adesso. Il formato delle URL dei contenuti è un aspetto importante di un cms e richiederà ulteriore lavoro. Al momento va data la precedenza ad alcune parti essenziali dell'applicazione che mancano completamente, ad esempio la gestione del backend di cui non abbiamo scritto neppure una riga. Si inizierà dalla prossima volta.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8144363429249550121?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/gLpUwE3RPBQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8144363429249550121/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8144363429249550121?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8144363429249550121?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html" title="Generare URL SEF in symfony" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0QHRng6cCp7ImA9WxNUEks.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5445246836473799670</id><published>2009-11-03T16:13:00.000+01:00</published><updated>2009-11-03T16:15:37.618+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-03T16:15:37.618+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, elenco articoli per etichetta</title><content type="html">&lt;p&gt;Come si è visto nell'articolo precedente i link del menù &lt;em&gt;Etichette&lt;/em&gt; hanno questo formato&lt;/p&gt;

&lt;p&gt;&lt;code&gt;article/label/id/x&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Dove x è l'ID del record etichetta.&lt;/p&gt;

&lt;p&gt;Dobbiamo scrivere il codice per l'azione &lt;strong&gt;label&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt; ed il relativo template che determinerà l'aspetto della pagina con l'elenco degli articoli catalogati sotto una certa etichetta. L'elenco può naturalmente essere anche lungo quindi avremo bisogno di una funzione di paginazione, symfony ci mette a disposizione una classe dedicata a questo scopo.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeLabel(sfWebRequest $request)
  {
    $this-&amp;gt;forward404Unless(
      $this-&amp;gt;label = Doctrine::getTable('LyraLabel')
        -&amp;gt;find($request-&amp;gt;getParameter('id'))
    );
    $this-&amp;gt;pager = new sfDoctrinePager('LyraLabel', 25);
    $this-&amp;gt;pager-&amp;gt;setQuery($this-&amp;gt;label-&amp;gt;getItemsQuery());
    $this-&amp;gt;pager-&amp;gt;setPage($request-&amp;gt;getParameter('page', 1));
    $this-&amp;gt;pager-&amp;gt;init();
  }
...
}
&lt;/pre&gt;
All'oggetto &lt;strong&gt;pager&lt;/strong&gt; viene passata la query per la selezione degli articoli. Si imposta un valore costante (25) per il numero di record per pagina, in seguito questo parametro dovrà essere gestito dalla configurazione. La creazione della query avviene nel modello.

&lt;pre&gt;
lib/model/doctrine/LyraLabel.class.php

class LyraLabel extends BaseLyraLabel
{
  public function getItemsQuery() {
    $q = Doctrine::getTable('LyraArticle')
      -&amp;gt;getActiveItemsQuery();

    $q-&amp;gt;innerJoin($q-&amp;gt;getRootAlias().'.ArticleLabels l')
      -&amp;gt;andWhere('l.id = ?', $this-&amp;gt;getId());
    return $q;
  }
  ...
}
&lt;/pre&gt;
&lt;p&gt;Interessante notare la sintassi &lt;strong&gt;DQL&lt;/strong&gt; (Doctrine Query Language) usata per la query. Le tabelle articoli (&lt;em&gt;articles&lt;/em&gt;, modello &lt;strong&gt;LyraArticle&lt;/strong&gt;) ed etichette (&lt;em&gt;labels&lt;/em&gt;, modello &lt;strong&gt;LyraLabel&lt;/strong&gt;) sono tra loro in una relazione molti a molti tramite una tabella intermedia (&lt;em&gt;article_label&lt;/em&gt;, modello &lt;strong&gt;LyraArticleLabel&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;La relazione è definita in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt; (vedere relations di &lt;strong&gt;LyraArticle&lt;/strong&gt;) ed identificata come &lt;strong&gt;ArticleLabels&lt;/strong&gt;: questo identificatore è l'unica informazione che passiamo al metodo &lt;strong&gt;innerJoin()&lt;/strong&gt;, come si vede non ci sono clausole ON né nomi di tabelle in quanto tutte le informazioni necessarie a Doctrine per creare la query si trovano nello schema.&lt;/p&gt;

&lt;p&gt;Veniamo al template &lt;em&gt;labelSuccess.php&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/labelSuccess.php

&amp;lt;?php 
slot('page_title', $label-&amp;gt;title);
...
&lt;/pre&gt;

&lt;p&gt;Impostiamo uno &lt;strong&gt;slot&lt;/strong&gt; per visualizzare il titolo della pagina. Con gli slot si può inserire del contenuto in zone predeterminate del layout.&lt;/p&gt;

&lt;pre&gt;
...
include_partial('article/list', array('items'=&amp;gt;$pager-&amp;gt;getResults()));
?&amp;gt;
&amp;lt;?php if ($pager-&amp;gt;haveToPaginate()): ?&amp;gt;
  &amp;lt;?php 
    $base = 'article/label?id=' . $label-&amp;gt;getId() . '&amp;amp;page=';
  ?&amp;gt;
  &amp;lt;div class=&amp;quot;pagination&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to('First', $base . '1');?&amp;gt;
    &amp;lt;?php echo link_to('Prev', $base . $pager-&amp;gt;getPreviousPage());?&amp;gt;
    &amp;lt;?php foreach ($pager-&amp;gt;getLinks() as $page): ?&amp;gt;
      &amp;lt;?php if ($page == $pager-&amp;gt;getPage()): ?&amp;gt;
        &amp;lt;?php echo $page ?&amp;gt;
      &amp;lt;?php else:
        echo link_to($page, $base . $page);
      endif; ?&amp;gt;
    &amp;lt;?php endforeach; ?&amp;gt;
    &amp;lt;?php echo link_to('Next', $base . $pager-&amp;gt;getNextPage());?&amp;gt;
    &amp;lt;?php echo link_to('Last', $base . $pager-&amp;gt;getLastPage());?&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&amp;lt;!-- Fine  labelSuccess.php --&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Per la visualizzazione dell'elenco articoli riutilizziamo il partial creato per la prima pagina: di ogni articolo sarà visualizzato il titolo, la data e il sommario con link 'leggi tutto'. Il metodo &lt;strong&gt;getResults()&lt;/strong&gt; dell'oggetto pager restituisce gli articoli da visualizzare in base al numero di pagina che abbiamo impostato nell'azione. La parte restante del template serve alla visualizzazione dei link per la paginazione.&lt;/p&gt;

&lt;p&gt;Resta da aggiungere al layout l'istruzione per includere il contenuto dello slot impostato nel template.&lt;/p&gt;

&lt;p&gt;Questa parte&lt;/p&gt;
&lt;pre&gt;
apps/frontend/templates/layout.php
...
&amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;Titolo pagina&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;va modificata in questo modo&lt;/p&gt;
&lt;pre&gt;
apps/frontend/templates/layout.php
...
&amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;&amp;lt;?php include_slot('page_title'); ?&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;Tutto questo si trova nella &lt;strong&gt;revisione 17&lt;/strong&gt; su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5445246836473799670?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/mPj5AkQRMCo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5445246836473799670/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-elenco-articoli-per-etichetta.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5445246836473799670?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5445246836473799670?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-elenco-articoli-per-etichetta.html" title="Lyra, elenco articoli per etichetta" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CkQFR3c9eyp7ImA9WxNUEUo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4162375033990089189</id><published>2009-11-02T14:57:00.000+01:00</published><updated>2009-11-02T14:58:36.963+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-02T14:58:36.963+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, componente etichette</title><content type="html">&lt;p&gt;Sui blog o altri siti che pubblicano notizie e articoli è abbastanza comune trovare un menù con una lista di collegamenti a pagine che mostrano l'elenco degli articoli appartenenti ad una determinata categoria. Un esempio è data dal riquadro &lt;em&gt;Etichette&lt;/em&gt; presente nella colonna destra delle pagine di questo blog. Vediamo come implementare questa funzione in Lyra.&lt;/p&gt;

&lt;p&gt;Quando si devono visualizzare informazioni in una colonna laterale o comunque in un'area diversa dal corpo principale della pagina si può utilizzare un &lt;strong&gt;componente&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vediamo innanzi tutto come il componente viene richiamato dal layout:&lt;/p&gt;

&lt;pre&gt;
apps/frontend/templates/layout.php

...
&amp;lt;div id=&amp;quot;rightbar&amp;quot;&amp;gt;
  &amp;lt;?php include_component('article', 'labels', array('catalog'=&amp;gt;'Argomento')) ?&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;Gli argomenti passati alla funzione helper &lt;strong&gt;include_component()&lt;/strong&gt; sono nell'ordine: il nome del modulo, il nome del componente, un array opzionale contenente parametri che saranno accessibili dal codice del componente (vedremo subito come).&lt;/p&gt;

&lt;p&gt;In symfony un componente consiste in una action ed un template.&lt;/p&gt;

&lt;h2&gt;Action di un componente&lt;/h2&gt;

&lt;p&gt;Viene implementata come metodo &lt;strong&gt;[componente]Execute()&lt;/strong&gt; di una classe &lt;strong&gt;[modulo]Components&lt;/strong&gt; derivata da &lt;strong&gt;sfComponents&lt;/strong&gt; e definita in &lt;em&gt;apps/frontend/modules/[modulo]/actions/components.class.php&lt;/em&gt;. Nel nostro caso (componente &lt;strong&gt;labels&lt;/strong&gt;, modulo &lt;strong&gt;article&lt;/strong&gt;) quindi&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/components.class.php

class articleComponents extends sfComponents
{
  public function executeLabels(sfWebRequest $request)
  {
    $catalog = Doctrine::getTable('LyraCatalog')
      -&amp;gt;findOneByName($this-&amp;gt;catalog);
    $this-&amp;gt;tree = $catalog-&amp;gt;getLabelTree();
  }
}
&lt;/pre&gt;
 
&lt;p&gt;&lt;strong&gt;$this-&gt;catalog&lt;/strong&gt; contiene il valore del parametro &lt;em&gt;catalog&lt;/em&gt; contenuto nell'array di parametri passato ad &lt;strong&gt;include_component()&lt;/strong&gt;. Questo valore viene passato a &lt;strong&gt;findOneByName()&lt;/strong&gt; per ottenere l'oggetto record corrispondente, poi con &lt;strong&gt;getLabelTree()&lt;/strong&gt; otteniamo l'albero delle etichette associate al catalogo. Questo metodo va ovviamente implementato nella classe &lt;strong&gt;LyraCatalog&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraCatalog.class.php

class LyraCatalog extends BaseLyraCatalog
{
  public function getLabelTree()
  {
    $q = Doctrine_Query::create()
      -&amp;gt;from('LyraLabel l')
      -&amp;gt;where('l.catalog_id = ? and l.level = 0', $this-&amp;gt;getId());

    $root = $q-&amp;gt;fetchOne();

    return $root-&amp;gt;getNode()-&amp;gt;getDescendants();
  }
}
&lt;/pre&gt;
&lt;p&gt;Per capire il codice va  tenuto presente che esistono relazioni gerarchiche tra i record della tabella Etichette. I record a livello zero (ne esiste uno per ogni catalogo) sono le radici dell'albero delle categorie associate ad un catalogo. Con una query selezioniamo il record radice del catalogo (&lt;strong&gt;$root&lt;/strong&gt;): i suoi discendenti sono l'albero delle categorie che ci interessa e che possiamo ottenere semplicemente con la chiamata ad un singolo metodo &lt;strong&gt;getDescendants()&lt;/strong&gt; perché la tabella Etichette è costruita come &lt;strong&gt;nested set&lt;/strong&gt; (vedere &lt;strong&gt;LyraLabel&lt;/strong&gt; in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;Template di un componente&lt;/h2&gt;

&lt;p&gt;Il template di un componente non è diverso da quelli già visti per le singole azioni di un modulo.&lt;/p&gt;

&lt;pre&gt;
&amp;lt;h4&amp;gt;&amp;lt;?php echo __('HEAD_LABELS')?&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;ul class=&amp;quot;label-list&amp;quot;&amp;gt;
&amp;lt;?php foreach($tree as $node): ?&amp;gt;
  &amp;lt;li class=&amp;quot;lev&amp;lt;?php echo $node-&amp;gt;getLevel(); ?&amp;gt;&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to($node-&amp;gt;getName(),'article/label?id='.$node-&amp;gt;getId()); ?&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;?php endforeach ?&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Tutto dovrebbe essere abbastanza chiaro. Faccio notare solo che:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;$tree&lt;/strong&gt; nel template contiene il valore della proprietà assegnata nell'azione con &lt;strong&gt;$this-&gt;tree&lt;/strong&gt; con il meccanismo già visto in precedenza;&lt;/li&gt;

&lt;li&gt;sarà necessario creare una nuova azione (&lt;strong&gt;label&lt;/strong&gt;) nel modulo &lt;strong&gt;article&lt;/strong&gt; che servirà a visualizzare la pagina con l'elenco degli articoli appartenenti alla categoria. La funzione helper &lt;strong&gt;link_to()&lt;/strong&gt; genera un link per questa azione.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SutggyKg1qI/AAAAAAAAAHQ/l30f-09-mv4/s1600-h/lyra-menu.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 269px; height: 131px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SutggyKg1qI/AAAAAAAAAHQ/l30f-09-mv4/s320/lyra-menu.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5398514694671619746" /&gt;&lt;/a&gt;
&lt;p&gt;L'implementazione dell'azione &lt;strong&gt;label&lt;/strong&gt; sarà fatta la prossima volta. Per il momento quanto sviluppato fino a questo punto si trova nella &lt;strong&gt;revisione 16&lt;/strong&gt; su Google Code.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4162375033990089189?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/cEfB7zlBwQ0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4162375033990089189/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-componente-etichette.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4162375033990089189?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4162375033990089189?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-componente-etichette.html" title="Lyra, componente etichette" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/SutggyKg1qI/AAAAAAAAAHQ/l30f-09-mv4/s72-c/lyra-menu.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0cASXo5eSp7ImA9WxNVGEQ.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3602503366515110939</id><published>2009-10-30T09:13:00.004+01:00</published><updated>2009-10-30T09:24:08.421+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-30T09:24:08.421+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Personalizzare l'aspetto dei form in symfony</title><content type="html">&lt;p&gt;I form per l'inserimento e la modifica dei dati sono una parte importante di un cms come di ogni altra applicazione web. Durante lo sviluppo di Lyra (vedi &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-inserimento-e-modifica-articoli.html"&gt;Lyra, inserimento e modifica articoli&lt;/a&gt;) abbiamo già incontrato diversi esempi delle possibilità offerte dal framework per personalizzare l'aspetto ed il funzionamento dei form. Ad esempio:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;variare l'ordine posizione dei campi;&lt;/li&gt;
&lt;li&gt;modificare l'etichetta;&lt;/li&gt;
&lt;li&gt;modificare il tipo di controllo usato dall'utente per inserire i dati (&lt;strong&gt;widget&lt;/strong&gt; nella terminologia di symfony);&lt;/li&gt;
&lt;li&gt;aggiungere dinamicamente campi e relativi widget;&lt;/li&gt;
&lt;li&gt;impostare regole di validazione.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;È possibile però fare molto di più ed arrivare a personalizzare completamente il codice HTML che determina la struttura del form. Per fare questo è necessario creare una classe &lt;strong&gt;form formatter&lt;/strong&gt; personalizzata, ne vedremo  un esempio tra poco realizzando il form per l'inserimento di un commento ad un articolo. &lt;/p&gt;

&lt;h2&gt;Form commenti&lt;/h2&gt;

&lt;p&gt;Dopo aver sviluppato le funzioni di visualizzazione dei commenti è necessario creare il form per l'invio di un commento. La classe relativa a questo form (&lt;strong&gt;LyraCommentForm&lt;/strong&gt;) è stata generata automaticamente a partire dalle informazioni contenute nello schema dati. &lt;/p&gt;

&lt;p&gt;Poiché il form viene visualizzato dopo la lista dei commenti sulla vista a tutta pagina dell'articolo, l'oggetto relativo viene creato nell'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt; per essere passato al template.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeShow(sfWebRequest $request)
  {
    ...
    $this-&amp;gt;form = new LyraCommentForm();
    $this-&amp;gt;form-&amp;gt;setDefault('article_id', $this-&amp;gt;item-&amp;gt;getId());
  }
}
&lt;/pre&gt;
&lt;p&gt;Viene impostato il valore di default del campo &lt;strong&gt;article_id&lt;/strong&gt; per legare il commento all'articolo.&lt;/p&gt;

&lt;p&gt;Come si è fatto per la lista commenti, la visualizzazione del form viene demandata ad un partial richiamato in &lt;em&gt;showSuccess.php&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/showSuccess.php

if($form) {
  include_partial('article/comment_form', array('form'=&amp;gt;$form));
}
?&amp;gt; // fine showSuccess.php
&lt;/pre&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/_comment_form.php

&amp;lt;h3 id=&amp;quot;comment-form&amp;quot;&amp;gt;&amp;lt;?php echo __('HEAD_SUBMIT_COMMENT') ?&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;div id=&amp;quot;form-wrapper&amp;quot;&amp;gt;
  &amp;lt;form action=&amp;quot;&amp;lt;?php echo url_for('article/comment?id=' . $form['article_id']-&amp;gt;getValue()) ?&amp;gt;&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;?php echo $form ?&amp;gt;
    &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot; /&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Come si vede dal valore dell'attributo &lt;strong&gt;action&lt;/strong&gt;, i dati inviati tramite il form vengono processati da un'azione &lt;strong&gt;comment&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt;. In questa fase non mi è sembrato utile creare un nuovo modulo nel frontend dedicato alla gestione dei commenti, in futuro potrebbe essere necessario farlo.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeComment(sfWebRequest $request)
  {
    $this-&amp;gt;forward404Unless($request-&amp;gt;isMethod('post'));
    $this-&amp;gt;item = Doctrine::getTable('LyraArticle')-&amp;gt;find($request-&amp;gt;getParameter('id'));
    $this-&amp;gt;forward404Unless($this-&amp;gt;item);
    $this-&amp;gt;form = new LyraCommentForm();
    $this-&amp;gt;processCommentForm($request, $this-&amp;gt;form);
    $this-&amp;gt;comments = $this-&amp;gt;item-&amp;gt;getActiveComments();
    $this-&amp;gt;setTemplate('show');
  }
  ...
  protected function processCommentForm(sfWebRequest $request, sfForm $form)
  {
    $form-&amp;gt;bind($request-&amp;gt;getParameter($form-&amp;gt;getName()));
    if ($form-&amp;gt;isValid())
    {
      $comment = $form-&amp;gt;save();
      $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_COMMENT_SAVED');
      $this-&amp;gt;redirect('article/show?id='.$comment-&amp;gt;getArticleId());
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Una volta che il form è stato salvato senza errori si viene reindirizzati alla pagina dell'articolo. Il metodo &lt;strong&gt;setFlash()&lt;/strong&gt; imposta un messaggio di conferma da visualizzzare dopo il redirect&lt;/p&gt;

&lt;p&gt;Resta da personalizzare la classe &lt;strong&gt;LyraCommentForm&lt;/strong&gt;. Un primo livello di personalizzazione avviene nel metodo &lt;strong&gt;configure()&lt;/strong&gt; della classe. Si tratta di operazioni in gran parte già viste e comunque ben documentate.&lt;/p&gt;

&lt;pre&gt;
class LyraCommentForm extends BaseLyraCommentForm
{
  public function configure()
  {
    $this-&amp;gt;removeFields();
&lt;/pre&gt;
&lt;p&gt;Si rimuovono i campi che non devono essere visualizzati sul form. Perché si utilizzi un metodo protetto per questa operazione sarà chiaro quando svilupperemo la gestione di backend dei commenti, ma chi ha seguito il tutorial Jobeet lo sa già.&lt;/p&gt;
&lt;pre&gt;
    $this-&amp;gt;widgetSchema['article_id'] = new sfWidgetFormInputHidden();
&lt;/pre&gt;
&lt;p&gt;Come si è visto, il valore del campo &lt;strong&gt;article_id&lt;/strong&gt; viene impostato nell'azione e non deve essere modificato dall'utente: si cambia quindi il tipo di widget da testo a campo nascosto. &lt;/p&gt;
&lt;pre&gt;
    $this-&amp;gt;widgetSchema['author_name']-&amp;gt;setLabel('AUTHOR_NAME');
    $this-&amp;gt;widgetSchema['author_email']-&amp;gt;setLabel('AUTHOR_EMAIL');
    $this-&amp;gt;widgetSchema['author_url']-&amp;gt;setLabel('AUTHOR_URL');
    $this-&amp;gt;widgetSchema['content']-&amp;gt;setLabel(false);
&lt;/pre&gt;

&lt;p&gt;Si modificano le etichette dei campi. Passando &lt;em&gt;false&lt;/em&gt; al metodo &lt;strong&gt;setLabel()&lt;/strong&gt; si rimuove l'etichetta per il campo &lt;strong&gt;content&lt;/strong&gt; (l'area di testo per l'inserimento del commento).&lt;/p&gt;
&lt;pre&gt;
    $this-&amp;gt;widgetSchema['content']-&amp;gt;setAttribute('rows',12);
    $this-&amp;gt;widgetSchema['content']-&amp;gt;setAttribute('cols',45);
    $this-&amp;gt;widgetSchema-&amp;gt;setHelp('author_email','AUTHOR_EMAIL_HELP');
&lt;/pre&gt;
&lt;p&gt;Si impostano attributi per l'area di testo ed un testo di aiuto che sarà visualizzato sotto il campo e-mail.&lt;/p&gt;
&lt;pre&gt;
    $this-&amp;gt;validatorSchema['author_name']-&amp;gt;addMessage('required','AUTHOR_NAME_REQUIRED');
    $this-&amp;gt;validatorSchema['content']-&amp;gt;addMessage('required','CONTENT_REQUIRED');
    $this-&amp;gt;validatorSchema['author_email'] = new sfValidatorEmail(
      array('required'=&amp;gt;true),
      array('required'=&amp;gt;'AUTHOR_EMAIL_REQUIRED','invalid'=&amp;gt;'AUTHOR_EMAIL_INVALID')
    );
    $this-&amp;gt;validatorSchema['author_url'] = new sfValidatorUrl(
      array('required'=&amp;gt;false),
      array('invalid'=&amp;gt;'AUTHOR_URL_INVALID')
    );
&lt;/pre&gt;
&lt;p&gt;Si impostano i messaggi di errore che sono visualizzati in caso di mancata validazione del campo. Per i campi &lt;strong&gt;author_email&lt;/strong&gt; e &lt;strong&gt;author_url&lt;/strong&gt; si impostano validatori standard che bloccano l'inserimento di un indirizzo e-mail o URL non validi.&lt;/p&gt;
&lt;pre&gt;
    $this-&amp;gt;widgetSchema-&amp;gt;setFormFormatterName('LyraComment');
&lt;/pre&gt;
&lt;p&gt;Imposta il nome della classe &lt;strong&gt;form formatter&lt;/strong&gt; da utilizzare per il form.&lt;/p&gt;
&lt;pre&gt;
  } //fine configure()
  protected function removeFields()
  {
    unset($this['created_at'], $this['updated_at'], $this['is_active']);
  }
}// fine LyraCommentForm
&lt;/pre&gt;
&lt;h2&gt;Form formatter personalizzati&lt;/h2&gt;

&lt;p&gt;Tornando al partial che visualizza il form (&lt;em&gt;_comment_form.php&lt;/em&gt;), si nota che nel codice sono stati inclusi solo i tag &amp;lt;form&amp;gt; e &amp;lt;input&amp;gt; per il pulsante &lt;em&gt;Invia&lt;/em&gt;, mentre il codice HTML per tutti campi e le etichette è generato da una singola istruzione&lt;/p&gt;

&lt;pre&gt;
echo $form;
&lt;/pre&gt;

&lt;p&gt;I tag (&amp;lt;input&amp;gt;, &amp;lt;textarea&amp;gt;,&amp;lt;select&amp;gt;) e gli attributi dei singoli campi sono determinati dal &lt;strong&gt;widget&lt;/strong&gt; creato per il campo nella classe base del form ed eventualmente modificato nel metodo &lt;strong&gt;configure()&lt;/strong&gt; della classe derivata. Anche i tag &amp;lt;label&amp;gt; sono generati grazie ad informazioni impostate nella classe del form, invece il codice HTML che 'incornicia' campi ed etichette viene generato da una classe &lt;strong&gt;form formatter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nel framework viene definita una classe base &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/vendor/symfony/lib/widget/sfWidgetFormSchemaFormatter.class.php"&gt;sfWidgetFormSchemaFormatter&lt;/a&gt; e due classi da essa derivate: &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/vendor/symfony/lib/widget/sfWidgetFormSchemaFormatterTable.class.php"&gt;sfWidgetFormSchemaFormatterTable&lt;/a&gt; e &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/vendor/symfony/lib/widget/sfWidgetFormSchemaFormatterList.class.php"&gt;sfWidgetFormSchemaFormatterList&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sfWidgetFormSchemaFormatterTable&lt;/strong&gt; verrà utilizzata per tutti i form a meno che non si indichi al framework (con il metodo &lt;strong&gt;setFormFormatterName()&lt;/strong&gt; che abbiamo visto sopra) di utilizzare la classe &lt;strong&gt;sfWidgetFormSchemaFormatterList&lt;/strong&gt; o una nostra classe personalizzata al posto di quella predefinita.&lt;/p&gt;

&lt;p&gt;Questa è la classe creata per il form commenti di Lyra.&lt;/p&gt;

&lt;pre&gt;
class sfWidgetFormSchemaFormatterLyraComment extends sfWidgetFormSchemaFormatter
{
  protected
      $rowFormat = '&amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;%error%%field%%label%%help%%hidden_fields%&amp;lt;/div&amp;gt;',
      $helpFormat = '&amp;lt;div class=&amp;quot;field-help&amp;quot;&amp;gt;%help%&amp;lt;/div&amp;gt;';
}
&lt;/pre&gt;

&lt;p&gt;Il file con la definizione della classe va inserito nella cartella &lt;em&gt;lib&lt;/em&gt; del proprio progetto. La classe deve essere derivata dal formatter base e il nome deve essere sfWidgetFormSchemaFormatter + il nome che vogliamo dare al nostro formatter e che passaremo a &lt;strong&gt;setFormFormatterName()&lt;/strong&gt; nel metodo &lt;strong&gt;configure()&lt;/strong&gt; della classe form.&lt;/p&gt;

&lt;p&gt;A me serviva solo includere in tag &amp;lt;div&amp;gt; campo ed etichetta, posizionare l'etichetta a destra del campo sulla stessa riga, gli eventuali messaggi di errori sopra il campo e creare un ulteriore &amp;lt;div&amp;gt; per racchiudere il testo di aiuto che viene mostrato sotto i campi. Quindi è stato sufficiente personalizzare le proprietà &lt;strong&gt;$rowFormat&lt;/strong&gt; e &lt;strong&gt;$helpFormat&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In questo modo il form appare così:&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/Sup2uwBlxqI/AAAAAAAAAHI/cZ0mbohNKNM/s1600-h/lyra-form-commenti.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 313px; height: 320px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/Sup2uwBlxqI/AAAAAAAAAHI/cZ0mbohNKNM/s320/lyra-form-commenti.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5398257648894592674" /&gt;&lt;/a&gt;
&lt;p&gt;Il tutto è incluso nella &lt;strong&gt;revisione 15&lt;/strong&gt;. Chi si allinea al repository, deve ricordarsi di eseguire subito dopo il checkout il comando&lt;/p&gt;

&lt;pre&gt;
./symfony cc
&lt;/pre&gt;
&lt;p&gt;Male non fa mai, ma è obbligatorio quando si aggiungono nuove classi in &lt;em&gt;lib&lt;/em&gt;. Con queste modifiche è già possibile inserire commenti, mancando però le funzioni di moderazione nel backend, per vedere i nuovi commenti nel frontend è necessario impostare manualmente il campo &lt;strong&gt;is_active&lt;/strong&gt; a 1 nel database in quanto i nuovi commenti vengono salvati con il valore predefinito 0 (cioè non pubblicato).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3602503366515110939?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/bOJePq2HhCCt31uIODzO4Vt7aU4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bOJePq2HhCCt31uIODzO4Vt7aU4/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/bOJePq2HhCCt31uIODzO4Vt7aU4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bOJePq2HhCCt31uIODzO4Vt7aU4/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/Bt1QykpAjkE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3602503366515110939/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/personalizzare-form-in-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3602503366515110939?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3602503366515110939?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/personalizzare-form-in-symfony.html" title="Personalizzare l'aspetto dei form in symfony" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/Sup2uwBlxqI/AAAAAAAAAHI/cZ0mbohNKNM/s72-c/lyra-form-commenti.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CEEGRHs8eSp7ImA9WxNVF04.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6899771956516751316</id><published>2009-10-28T13:22:00.000+01:00</published><updated>2009-10-28T13:23:45.571+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-28T13:23:45.571+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, visualizzazione commenti articolo</title><content type="html">&lt;p&gt;Quando abbiamo generato i dati di prova per lo sviluppo ed il test iniziale dell'applicazione (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-dati-di-prova.html"&gt;symfony, creazione dati di prova (fixtures)&lt;/a&gt;), si sono inclusi alcuni record di commenti legati agli articoli. Non è stata per ora scritta alcuna funzione di visualizzazione di questi dati ed è il momento di provvedere.&lt;/p&gt;

&lt;p&gt;La gestione dei commenti sarà integrata nel cms, le funzionalità minime che dovranno essere previste sono almeno le seguenti:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;attivazione e disattivazione dei commenti su singoli articoli o categorie (etichette);&lt;/li&gt;
&lt;li&gt;possibilità di limitare solo agli utenti registrati l'inserimento dei commenti o consentirlo anche agli utenti anonimi;&lt;/li&gt;
&lt;li&gt;pubblicazione automatica o moderazione dei commenti;&lt;/li&gt;
&lt;li&gt;protezione da spam tramite captcha o altre misure anti-robot sul modulo di inserimento commenti;&lt;/li&gt;
&lt;li&gt;filtri contro i contenuti o link indesiderati nei commenti;&lt;/li&gt;
&lt;li&gt;notifiche dell'inserimento / approvazione di un commento;&lt;/li&gt;
&lt;li&gt;possibilità di ricevere i commenti inseriti su un articolo / categoria via e-mail o feed.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;La lista può non essere completa. Chiaramente non tutto può essere sviluppato adesso, quindi inizieremo con le funzioni di visualizzazione.&lt;/p&gt;

&lt;p&gt;Poiché la lista dei commenti viene visualizzata quando l'articolo è a tutta pagina dovremo intervenire sul codice dell'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt; ed il relativo template (&lt;em&gt;showSuccess.php&lt;/em&gt;), mentre la funzione per estrarre dal database la lista dei commenti relativi ad un determinato articolo deve essere implementata in una classe del modello.&lt;/p&gt;

&lt;h2&gt;Azione&lt;/h2&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeShow(sfWebRequest $request)
  {   
    ...
    $this-&amp;gt;comments = $this-&amp;gt;item-&amp;gt;getActiveComments();
  }
...
} // fine class articleActions
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$this-&gt;item&lt;/strong&gt; contiene il record articolo da visualizzare ritornato dal metodo &lt;strong&gt;find()&lt;/strong&gt; nella parte del codice che ho omesso e che abbiamo visto in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-visualizzazione-articolo.html"&gt;Lyra, visualizzazione articolo&lt;/a&gt;.
&lt;/p&gt;
&lt;h2&gt;Modello&lt;/h2&gt;

&lt;h3&gt;LyraArticle&lt;/h3&gt;
&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
...
  public function getActiveComments()
  {
    return $this-&amp;gt;getActiveCommentsQuery()
      -&amp;gt;execute();
  }
  protected function getActiveCommentsQuery()
  {
    $q = Doctrine::getTable('LyraComment')
      -&amp;gt;getActiveItemsQuery();

    $q-&amp;gt;andWhere($q-&amp;gt;getRootAlias() .'.article_id = ?', $this-&amp;gt;getId());
      
    return $q;
  }
} // fine classe LyraArticle
&lt;/pre&gt;

&lt;h3&gt;LyraCommentTable&lt;/h3&gt;

&lt;pre&gt;
lib/model/doctrine/LyraCommentTable.class.php

class LyraCommentTable extends Doctrine_Table
{
  public function getActiveItemsQuery() {
    $q = $this-&amp;gt;createQuery('c');

    $q-&amp;gt;andWhere('c.is_active = ?', true);
    $q-&amp;gt;addOrderBy('c.created_at DESC');

    return $q;
  }
}
&lt;/pre&gt;

&lt;p&gt;La query SQL per la selezione dei commenti da visualizzare viene generata da due classi: &lt;strong&gt;LyraCommentTable&lt;/strong&gt; genera la parte che seleziona i commenti pubblicati (campo &lt;strong&gt;is_active&lt;/strong&gt; è &lt;em&gt;true&lt;/em&gt;) e imposta il criterio di ordinamento, &lt;strong&gt;LyraArticle&lt;/strong&gt; aggiunge un criterio di selezione basato sull'ID articolo.&lt;/p&gt;

&lt;p&gt;Sembrano troppi metodi per costruire una query semplice, ma il fatto è che non resterà così semplice: dovranno necessariamente essere previste delle condizioni di visualizzazione e dei criteri di selezione e ordinamento dei commenti dipendenti da parametri di configurazione. Ad esempio:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;la configurazione articoli dovrà prevedere la possibilità di disattivare la visualizzazione dei commenti su un singolo articolo;&lt;/li&gt;
&lt;li&gt;la configurazione commenti dovrà consentire di impostare diversi criteri di ordinamento in aggiunta all'ordinamento per data discendente fisso come adesso.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;Mi sembra opportuno che la prima condizione sia verificata in &lt;strong&gt;LyraArticle&lt;/strong&gt; e il secondo parametro impostato in &lt;strong&gt;LyraCommentTable&lt;/strong&gt;: tra tutte le condizioni che determinano la visibilità e l'aspetto della lista dei commenti ogni classe gestisce quelle che dipendono dall'oggetto che le compete. Almeno al momento mi torna bene fatto così, se mi sbaglio ci sarà il tempo di correggere.&lt;/p&gt;

&lt;h2&gt;Template&lt;/h2&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/showSuccess.php
...
&amp;lt;?php
if(count($comments)) {
  include_partial('article/comments', array('comments'=&amp;gt;$comments));
}
?&amp;gt; // fine showSuccess.php
&lt;/pre&gt;
&lt;p&gt;Per visualizzare i commenti si utilizza un partial, ormai ne abbiamo incontrati diversi negli articoli precedenti.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/_comments.php

&amp;lt;?php use_helper('Date');?&amp;gt;
&amp;lt;h2 id=&amp;quot;comments&amp;quot;&amp;gt;&amp;lt;?php echo __('HEAD_COMMENTS') ?&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt;?php foreach($comments as $comment):?&amp;gt;
&amp;lt;div class=&amp;quot;comment-wrapper&amp;quot;&amp;gt;
&amp;lt;div class=&amp;quot;comment-header&amp;quot;&amp;gt;
  &amp;lt;?php
  $author = $comment-&amp;gt;getAuthorName();
  if($comment-&amp;gt;getAuthorUrl()) {
    $author = link_to($author, $comment-&amp;gt;getAuthorUrl());
  }
  echo __('COMMENT_HEADER',
    array(
      '%name%'=&amp;gt;'&amp;lt;span class=&amp;quot;author&amp;quot;&amp;gt;' . $author . '&amp;lt;/span&amp;gt;',
      '%date%'=&amp;gt;'&amp;lt;span class=&amp;quot;date&amp;quot;&amp;gt;' . format_date($comment-&amp;gt;getCreatedAt(),'dd MMMM yyyy') . '&amp;lt;/span&amp;gt;',
      '%time%'=&amp;gt;'&amp;lt;span class=&amp;quot;time&amp;quot;&amp;gt;' . format_date($comment-&amp;gt;getCreatedAt(),'HH:mm') . '&amp;lt;/span&amp;gt;'
    ));
  ?&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;comment-content&amp;quot;&amp;gt;&amp;lt;?php echo $comment-&amp;gt;getContent()?&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;?php endforeach;?&amp;gt;
&lt;/pre&gt;
&lt;p&gt;L'unica cosa rilevante da notare è l'uso della funzione helper per la traduzione (&lt;strong&gt;__()&lt;/strong&gt;) per creare l'intestazione del commento, la riga che contiene autore, data ed eventuale link al sito web dell'autore. È la prima volta che ne vediamo l'uso con parametri (%name%, %date%, %time%). Nel file di lingua abbiamo:&lt;/p&gt;

&lt;pre&gt;
apps/frontend/i18n/it/messages.xml

...
  &amp;lt;trans-unit id=&amp;quot;20&amp;quot;&amp;gt;
    &amp;lt;source&amp;gt;COMMENT_HEADER&amp;lt;/source&amp;gt;
    &amp;lt;target&amp;gt;%name% il %date% alle %time%&amp;lt;/target&amp;gt;
  &amp;lt;/trans-unit&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;Che produce questa intestazione.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SueiJUOuTqI/AAAAAAAAAG4/l-ZFgDlbwU0/s1600-h/lyra-commenti1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 197px; height: 152px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SueiJUOuTqI/AAAAAAAAAG4/l-ZFgDlbwU0/s320/lyra-commenti1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5397460959359815330" /&gt;&lt;/a&gt;
&lt;p&gt;L'uso di una costante nel file di lingua oltre che alla traduzione serve a modificare l'intestazione senza toccare il codice. Ad esempio modificando COMMENT_HEADER in questo modo&lt;/p&gt;
&lt;pre&gt;
  &amp;lt;trans-unit id=&amp;quot;20&amp;quot;&amp;gt;
    &amp;lt;source&amp;gt;COMMENT_HEADER&amp;lt;/source&amp;gt;
    &amp;lt;target&amp;gt;il %date% alle %time% %name% ha scritto&amp;lt;/target&amp;gt;
  &amp;lt;/trans-unit&amp;gt;
&lt;/pre&gt;
&lt;p&gt;avremo questo risultato.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SueiRy6cfZI/AAAAAAAAAHA/LnWL26TbExs/s1600-h/lyra-commenti2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 251px; height: 72px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SueiRy6cfZI/AAAAAAAAAHA/LnWL26TbExs/s320/lyra-commenti2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5397461105035214226" /&gt;&lt;/a&gt;
&lt;p&gt;Sono state necessarie anche modifiche al file &lt;em&gt;web/css/main.css&lt;/em&gt; per inserire le classi necessarie alla formattazione dei commenti. I dettagli sono su Google Code (&lt;strong&gt;revisione 14&lt;/strong&gt;) come del resto tutto il codice riportato sopra. La prossima volta creeremo il form per l'inserimento di un commento.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6899771956516751316?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Q4f2wr1epJib7qVTnzA0lhDm0ck/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Q4f2wr1epJib7qVTnzA0lhDm0ck/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Q4f2wr1epJib7qVTnzA0lhDm0ck/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Q4f2wr1epJib7qVTnzA0lhDm0ck/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/FE_y-Wv3vRs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6899771956516751316/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-visualizzazione-commenti-articolo.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6899771956516751316?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6899771956516751316?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-visualizzazione-commenti-articolo.html" title="Lyra, visualizzazione commenti articolo" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/SueiJUOuTqI/AAAAAAAAAG4/l-ZFgDlbwU0/s72-c/lyra-commenti1.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkUESXY9fSp7ImA9WxNVE00.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-905167014361474755</id><published>2009-10-23T15:22:00.000+02:00</published><updated>2009-10-23T15:23:28.865+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-23T15:23:28.865+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Utilizzare FCKeditor in symfony</title><content type="html">&lt;p&gt;Chi ha avuto voglia di provare ad inserire o modificare un articolo con l'ultima revisione di Lyra avrà senz'altro notato la mancanza di un &lt;strong&gt;editor WYSIWYG&lt;/strong&gt;. Ho pensato che anche in questa fase iniziale dello sviluppo avrebbe fatto comodo avere a disposizione qualcosa di più di una semplice area di testo standard, anche solo per inserire dei contenuti di prova. &lt;/p&gt;

&lt;p&gt;Ho quindi seguito le istruzioni di &lt;a href="http://forum.symfony-project.org/index.php/m/76675/"&gt;questo post&lt;/a&gt; sul forum ufficiale di symfony per creare un widget che consenta l'inserimento di contenuti tramite &lt;strong&gt;FCKeditor&lt;/strong&gt;. La procedura è semplice:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;si copia il file scaricato dal forum (&lt;em&gt;sfWidgetFormTextareaFCKEditor.class.php&lt;/em&gt;) contenente la definizione della classe del widget nella cartella &lt;em&gt;lib&lt;/em&gt; del proprio progetto;&lt;/li&gt;
&lt;li&gt;si scarica il pacchetto dell'editor (FCKeditor non CKEditor) dal &lt;a href="http://ckeditor.com/download"&gt;sito del produttore&lt;/a&gt; e si scompatta in &lt;em&gt;web/js/&lt;/em&gt;. Come risultato della decompressione avremo una sottocartella &lt;em&gt;fckeditor&lt;/em&gt; da cui ho rimosso una serie di file non indispensabili come documentazione ed esempi vari;&lt;/li&gt;
&lt;li&gt;si includono le istruzioni per la creazione del widget nel metodo &lt;strong&gt;configure()&lt;/strong&gt; della classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraArticleForm.class.php"&gt;LyraArticleForm&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Alla fine bisogna ricordarsi di eseguire il comando&lt;/p&gt;

&lt;pre&gt;./symfony cc&lt;/pre&gt;

&lt;p&gt;La configurazione dell'editor (larghezza, altezza, tipo di barra degli strumenti) al momento è fissa, naturalmente in seguito dovranno essere previsti dei parametri di configurazione per consentire all'utente di personalizzare o anche disattivare l'editor per chi preferisce inserire i contenuti utilizzando l'area di testo semplice.&lt;/p&gt;

&lt;p&gt;Ho creato una barra di strumenti che include i pulsanti più comuni modificando il file &lt;em&gt;web/js/fckeditor/fckconfig.js&lt;/em&gt;. Da notare che la funzione di caricamento delle immagini sul server per ora è disabilitata.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SuDqQWRJ6fI/AAAAAAAAAGw/d9cNkL1ACxU/s1600-h/fckeditor.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 62px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SuDqQWRJ6fI/AAAAAAAAAGw/d9cNkL1ACxU/s320/fckeditor.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5395569920166193650" /&gt;&lt;/a&gt;
&lt;p&gt;Queste modifiche sono incluse nella &lt;strong&gt;revisione 13&lt;/strong&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-905167014361474755?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/1f697BqZLZQXqEscmMrr5Oc0xlE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/1f697BqZLZQXqEscmMrr5Oc0xlE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/1f697BqZLZQXqEscmMrr5Oc0xlE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/1f697BqZLZQXqEscmMrr5Oc0xlE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/GwZ33W6Y1wI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/905167014361474755/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/utilizzare-fckeditor-in-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/905167014361474755?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/905167014361474755?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/utilizzare-fckeditor-in-symfony.html" title="Utilizzare FCKeditor in symfony" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/SuDqQWRJ6fI/AAAAAAAAAGw/d9cNkL1ACxU/s72-c/fckeditor.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0cAQXs6fip7ImA9WxNVEU4.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6744924254036903582</id><published>2009-10-21T15:16:00.000+02:00</published><updated>2009-10-21T15:17:20.516+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-21T15:17:20.516+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, cataloghi ed etichette</title><content type="html">&lt;p&gt;In Lyra la categorizzazione dei contenuti avviene tramite &lt;strong&gt;etichette&lt;/strong&gt;, se si preferisce si possono chiamare categorie perché il concetto è quello. Le etichette sono suddivise in &lt;strong&gt;cataloghi&lt;/strong&gt;, questo per permettere criteri di classificazione multipli.&lt;/p&gt;

&lt;p&gt;Ad esempio, nei dati predisposti per il test iniziale dell'applicazione abbiamo creato questi cataloghi ed etichette:&lt;/p&gt;
&lt;pre&gt;
Argomento      &lt;- catalogo
  PHP          &lt;- etichetta
  Javascript   &lt;- etichetta
    Mootools   &lt;- etichetta
    jQuery     &lt;- etichetta

Livello        &lt;- catalogo
  Elementare   &lt;- etichetta
  Intermedio   &lt;- etichetta
  Avanzato     &lt;- etichetta
&lt;/pre&gt;

&lt;p&gt;In questo modo gli articoli possono essere classificati in base all'argomento e al livello di difficoltà. Quindi potremmo avere un articolo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"L'uso delle variabili in PHP"&lt;br /&gt;
Argomento (catalogo): PHP (etichetta)&lt;br /&gt;
Livello (catalogo): Elementare (etichetta)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ed un altro articolo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"La programmazione a oggetti in PHP"&lt;br /&gt;
Argomento (catalogo): PHP (etichetta)&lt;br /&gt;
Livello (catalogo): Avanzato (etichetta)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ogni etichetta appartiene ad un catalogo (e ad uno solo), esistono inoltre relazioni gerarchiche (padre-figlio) tra etichette (nell'esempio l'etichetta &lt;em&gt;Mootools&lt;/em&gt; è figlia di &lt;em&gt;Javascript&lt;/em&gt;). Ad un contenuto possono essere assegnate più etichette appartenenti a più cataloghi.&lt;/p&gt;

&lt;p&gt;I cataloghi utilizzabili per un contenuto dipendono dal &lt;strong&gt;tipo di contenuto&lt;/strong&gt;. In questo momento è gestito un solo tipo di contenuto (articolo), ma in futuro ne saranno creati altri ed ognuno potrà avere i propri cataloghi. Immaginiamo ad esempio un tipo di contenuto 'galleria di immagini': difficilmente le etichette usate per gli articoli, potranno andare bene per categorizzare una galleria, quindi si potrà creare un catalogo (o anche più di uno) specifico per questo tipo di contenuto.&lt;/p&gt;

&lt;p&gt;Quanto descritto sopra a parole trova riscontro nello schema dati in termini di relazioni tra le tabelle &lt;strong&gt;catalogs&lt;/strong&gt;, &lt;strong&gt;labels&lt;/strong&gt;, &lt;strong&gt;articles&lt;/strong&gt;, &lt;strong&gt;article_label&lt;/strong&gt; (tabella intermedia della relazione molti a molti tra articoli ed etichette), &lt;strong&gt;content_types&lt;/strong&gt;, &lt;strong&gt;content_type_catalog&lt;/strong&gt; (tabella intermedia della relazione molti a molti tra tipi di contenuto e cataloghi) e le rispettive classi del modello: &lt;strong&gt;LyraCatalog&lt;/strong&gt;, &lt;strong&gt;LyraLabel&lt;/strong&gt;, &lt;strong&gt;LyraArticle&lt;/strong&gt;,  &lt;strong&gt;LyraArticleLabel&lt;/strong&gt;, &lt;strong&gt;LyraContentType&lt;/strong&gt; e &lt;strong&gt;LyraContentTypeCatalog&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Riporto per ogni classe solo le chiavi primarie e le relazioni che ci interessano in questo momento come definite in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;
LyraCatalog:
   tableName: catalogs
...
   columns:
     id:
       type: integer(4)
       primary: true
       autoincrement: true
...
&lt;/pre&gt;
&lt;pre&gt;
LyraLabel:
   tableName: labels
...
   columns:
     id:
       type: integer(4)
       primary: true
       autoincrement: true
     catalog_id:
       type: integer(4)
...
   relations:
      LabelCatalog:
         class: LyraCatalog
         local: catalog_id
         foreign: id
         foreignAlias: CatalogLabels
         onDelete: CASCADE
...
&lt;/pre&gt;
&lt;pre&gt;
LyraArticle:
  tableName: articles
...
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
...
  relations:
    ArticleLabels:
      class: LyraLabel
      refClass: LyraArticleLabel
      local: article_id
      foreign: label_id
      foreignAlias: LabelArticles
...
&lt;/pre&gt;
&lt;pre&gt;
LyraArticleLabel:
   tableName: article_label
   columns:
      article_id:
         type: integer(4)
         primary: true
      label_id:
         type: integer(4)
         primary: true
   relations:
      Article:
         class: LyraArticle
         local: article_id
         foreign: id
         onDelete: CASCADE
      Label:
         class: LyraLabel
         local: label_id
         foreign: id
         onDelete: CASCADE
&lt;/pre&gt;
&lt;pre&gt;
LyraContentTypeCatalog:
   tableName: content_type_catalog
   columns:
      ctype_id:
         type: integer(4)
         primary: true
      catalog_id:
         type: integer(4)
         primary: true
   relations:
      ContentType:
        class: LyraContentType
        local: ctype_id
        foreign: id
        onDelete: CASCADE
      Catalog:
        class: LyraCatalog
        local: catalog_id
        foreign: id
        onDelete: CASCADE
&lt;/pre&gt;

&lt;p&gt;Con queste premesse e tenendo presente la gestione delle relazioni tra tabelle in Doctrine, si può capire meglio il codice per la personalizzazione del form di inserimento e modifica di un articolo lasciato in sospeso la volta scorsa. In particolare la parte che genera le liste di selezione utilizzate per assegnare una o più etichette all'articolo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/St5PipIVQ6I/AAAAAAAAAGo/xbcNlD2sHwM/s1600-h/lyra-labels.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 287px; height: 188px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/St5PipIVQ6I/AAAAAAAAAGo/xbcNlD2sHwM/s320/lyra-labels.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5394836860211381154" /&gt;&lt;/a&gt;
&lt;pre&gt;
lib/form/doctrine/LyraArticleForm.class.php

class LyraArticleForm extends BaseLyraArticleForm
{
  public function configure()
  {
     ...
     $ctype = Doctrine::getTable('LyraContentType')
      -&amp;gt;findOneByModule('article');
&lt;/pre&gt;
&lt;p&gt;Recuperiamo il record del tipo di contenuto gestito dal modulo &lt;strong&gt;article&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
     $def = array();
     if(!$this-&amp;gt;isNew()) {     
       $def = $this-&amp;gt;getObject()
         -&amp;gt;getArticleLabels()
         -&amp;gt;getPrimaryKeys();
     }
&lt;/pre&gt;
&lt;p&gt;Se stiamo modificando un articolo esistente (metodo &lt;strong&gt;isNew()&lt;/strong&gt; dell'oggetto form ritorna &lt;em&gt;false&lt;/em&gt;), il metodo &lt;strong&gt;getPrimaryKeys()&lt;/strong&gt; ci ritorna un array con i valori delle chiavi primarie dei record etichetta collegati all'articolo a loro volta ritornati da &lt;strong&gt;getArticleLabels()&lt;/strong&gt;: &lt;strong&gt;ArticleLabels&lt;/strong&gt; è il nome della relazione articoli-etichette definita nello schema, vedere sopra la classe &lt;strong&gt;LyraArticle&lt;/strong&gt;. Questi valori sono utilizzati come default per le liste di selezione delle etichette.&lt;/p&gt;

&lt;pre&gt;
    $after = 'subtitle';    
    foreach ($ctype-&amp;gt;ContentTypeCatalogs as $cg) {
        $query = Doctrine_Query::create()
          -&amp;gt;from('LyraLabel l')
          -&amp;gt;where('l.catalog_id = ? AND l.level &amp;gt; 0', $cg-&amp;gt;id);
        $k = 'label_'.$cg-&amp;gt;getId();
        $this-&amp;gt;widgetSchema[$k] = new sfWidgetFormDoctrineChoiceMany(array('model'=&amp;gt;'LyraLabel', 'query'=&amp;gt;$query, 'label'=&amp;gt;$cg-&amp;gt;name, 'default'=&amp;gt;$def, 'method'=&amp;gt;'getIndentName'));
        $this-&amp;gt;validatorSchema[$k] = new sfValidatorDoctrineChoiceMany(array('model'=&amp;gt;'LyraLabel', 'required'=&amp;gt;false));
        $this-&amp;gt;widgetSchema-&amp;gt;moveField($k, sfWidgetFormSchema::AFTER, $after);
        $after = $k;
    }
  } //fine configure()
&lt;/pre&gt;

&lt;p&gt;Viene creata una lista di selezione per ciascun catalogo legato al tipo di contenuto. Gli elementi delle liste vengono estratti dalla tabella &lt;em&gt;labels&lt;/em&gt; in base all'ID del catalogo: la query esclude l'elemento a livello zero nel &lt;strong&gt;nested set&lt;/strong&gt;, la radice del catalogo che non deve essere visualizzata nella lista.&lt;/p&gt;

&lt;p&gt;Per i dettagli sul widget utilizzato per le liste di selezione, rimando alla documentazione ufficiale: &lt;a href="http://www.symfony-project.org/forms/1_2/it/A-Widgets#chapter_a_widget_di_scelta"&gt;symfony forms - Widgets&lt;/a&gt;, paragrafo "Scelta legata ad un modello Doctrine".&lt;/p&gt;

&lt;p&gt;Le liste vengono posizionate (con &lt;strong&gt;moveField()&lt;/strong&gt;) una dopo l'altra dopo il campo &lt;strong&gt;subtitle&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;
  protected function doSave($con = null)
  {
    parent::doSave($con);
    $this-&amp;gt;saveLabels($con);
  }
&lt;/pre&gt;

&lt;p&gt;Implementando il metodo &lt;strong&gt;doSave()&lt;/strong&gt; in &lt;strong&gt;LyraArticleForm&lt;/strong&gt; possiamo eseguire codice quando il record principale viene salvato. In questo caso eseguiamo &lt;strong&gt;saveLabels()&lt;/strong&gt; per salvare i legami articolo / etichette.&lt;/p&gt;

&lt;p&gt;Tralascio l'esame in dettaglio del metodo &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraArticleForm.class.php?spec=svn12&amp;r=12"&gt;saveLabels()&lt;/a&gt;, richiamo solo l'attenzione di chi è interessato sui metodi &lt;strong&gt;link()&lt;/strong&gt; e &lt;strong&gt;unlink()&lt;/strong&gt; per costruire e rimuovere i legami tra i record delle tabelle Articoli ed Etichette che sono tra loro in una relazione molti a molti attraverso la tabella intermedia &lt;em&gt;article_label&lt;/em&gt; (classe modello &lt;strong&gt;LyraArticleLabel&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Nessuna modifica sul repository in quanto tutto il codice sopra riportato era già incluso nella &lt;strong&gt;revisione 12&lt;/strong&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6744924254036903582?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/RIMwV-rb6VZanw4c2Sa34BjOP50/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RIMwV-rb6VZanw4c2Sa34BjOP50/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/RIMwV-rb6VZanw4c2Sa34BjOP50/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RIMwV-rb6VZanw4c2Sa34BjOP50/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/SWqFrrV4eCQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6744924254036903582/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-cataloghi-ed-etichette.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6744924254036903582?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6744924254036903582?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-cataloghi-ed-etichette.html" title="Lyra, cataloghi ed etichette" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_unGpwtr1q-o/St5PipIVQ6I/AAAAAAAAAGo/xbcNlD2sHwM/s72-c/lyra-labels.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0AESXs9fSp7ImA9WxNWGUg.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-7254299662585826563</id><published>2009-10-19T13:24:00.002+02:00</published><updated>2009-10-19T13:28:28.565+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-19T13:28:28.565+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, inserimento e modifica articoli (frontend)</title><content type="html">&lt;p&gt;Come accennato quando sono state delineate le funzionalità principali della versione iniziale di Lyra, gli articoli potranno essere inseriti e modificati sia dal frontend che dal backend dell'applicazione.&lt;/p&gt;

&lt;p&gt;Il backend ancora non c'è, ma si può iniziare lo sviluppo delle funzioni di inserimento e modifica dei contenuti da frontend. Questo permette tra l'altro di spendere qualche parola sulla &lt;strong&gt;gestione dei form&lt;/strong&gt; in symfony.&lt;/p&gt;

&lt;h2&gt;Classi form&lt;/h2&gt;

&lt;p&gt;Quando si eseguono i comandi &lt;strong&gt;doctrine:build-forms&lt;/strong&gt;, &lt;strong&gt;doctrine:build-all&lt;/strong&gt; o &lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt; il framework utilizza le informazioni contenute nel file &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt; per genereare automaticamente le classi dei form: in particolare in &lt;em&gt;lib/form/doctrine/base&lt;/em&gt; troviamo le &lt;strong&gt;classi base&lt;/strong&gt; che vengono ricreate ogni volta che si esegue uno dei comandi appena nominati, in &lt;em&gt;lib/form/doctrine&lt;/em&gt; troviamo le &lt;strong&gt;classi derivate&lt;/strong&gt; dalle classi base che sono inizialmente create vuote e mai sovrascritte da comandi del framework. Quindi è in queste ultime che dovremo scrivere il codice per personalizzare un form. Al momento la classe che ci interessa è &lt;strong&gt;LyraArticleForm&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Azioni di inserimento, modifica, cancellazione&lt;/h2&gt;

&lt;p&gt;Procediamo con ordine iniziando dalle &lt;strong&gt;azioni&lt;/strong&gt;. Dato che l'inserimento, modifica e cancellazione dei dati sono operazioni frequenti che è facile standardizzare, symfony genera le relative azioni automaticamente quando si crea un modulo. I metodi che processano queste azioni per il modulo &lt;strong&gt;article&lt;/strong&gt; si trovano nella classe &lt;strong&gt;articleActions&lt;/strong&gt; in &lt;em&gt;apps/frontend/modules/article/actions/actions.class.php&lt;/em&gt;. Quelli che ci interessano (vedremo la cancellazione un'altra volta) sono:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;executeNew()&lt;/strong&gt; visualizza il form per l'inserimento di un articolo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;executeCreate()&lt;/strong&gt; valida il form di inserimento (chiamando il metodo protetto &lt;strong&gt;processForm()&lt;/strong&gt;) e, in mancanza di errori, crea un nuovo articolo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;executeEdit()&lt;/strong&gt; visualizza il form per la modifica di un articolo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;executeUpdate()&lt;/strong&gt; valida il form di modifica (sempre tramite &lt;strong&gt;processForm()&lt;/strong&gt;) e, in mancanza di errori, salva le modifiche.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;Il flusso tra queste operazioni, visualizzazione del form, validazione e salvataggio dello stesso con creazione o modifica del record o rivisualizzazione del form con errori in caso di mancata validazione, è spiegato in dettaglio nel tutorial ufficiale: &lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/10"&gt;Giorno 10: Form&lt;/a&gt;, vedere in particolare il diagramma sotto il paragarafo "L'azione del form".&lt;/p&gt;

&lt;h2&gt;Barra di amministrazione&lt;/h2&gt;

&lt;p&gt;Ovviamente gli utenti devono avere a disposizione dei link attraverso i quali eseguire una una delle azioni appena viste. Ispirandomi a &lt;strong&gt;Jobeet&lt;/strong&gt;, l'applicazione di esempio di symfony, creo una &lt;strong&gt;barra di amministrazione&lt;/strong&gt;: un semplice DIV che si sovrappone al contenuto e racchiude una lista di link. La barra dovrà essere visibile solo agli utenti autorizzati alle operazioni di &lt;strong&gt;inserimento&lt;/strong&gt;, &lt;strong&gt;modifica&lt;/strong&gt; e &lt;strong&gt;cancellazione&lt;/strong&gt; articoli, ma per il momento, mancando qualsiasi gestione utenti, sarà visibile a tutti. Questo non è un problema perché in questa fase l'applicazione viene eseguita solo in locale.&lt;/p&gt;

&lt;p&gt;Creiamo come prima cosa un partial&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/_admin_bar.php

&amp;lt;div id=&amp;quot;admin-bar&amp;quot;&amp;gt;
  &amp;lt;span class=&amp;quot;action&amp;quot;&amp;gt;&amp;lt;?php echo link_to(__('LINK_NEW'), 'article/new') ?&amp;gt;&amp;lt;/span&amp;gt;
  &amp;lt;span class=&amp;quot;action&amp;quot;&amp;gt;&amp;lt;?php echo link_to(__('LINK_EDIT'), 'article/edit?id='.$item-&amp;gt;getId()) ?&amp;gt;&amp;lt;/span&amp;gt;
  &amp;lt;span class=&amp;quot;action&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;&amp;lt;?php echo __('LINK_LOGOUT') ?&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Il formato delle rotte utilizzate nelle funzioni helper &lt;strong&gt;link_to()&lt;/strong&gt; è quello già visto in precedenza: modulo/azione/parametri. Per il testo (ancora) dei link si utilizza una costante che sarà sostituita dalla traduzione nella lingua selezionata per l'interfaccia. Di questo si occupa la funzione helper &lt;strong&gt;__()&lt;/strong&gt;, già incontrata in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-azioni-del-modulo-article.html"&gt;Lyra, azioni del modulo article&lt;/a&gt; a proposito della traduzione del link 'leggi tutto'. I file di traduzione si trovano nella cartella &lt;a href="http://code.google.com/p/lyra-cms/source/browse/#svn/trunk/apps/frontend/i18n/"&gt;apps/frontend/i18n&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Il partial viene richiamato dal template che visualizza l'articolo a tutta pagina.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/showSuccess.php

&amp;lt;?php include_partial('article/admin_bar', array('item' =&amp;gt; $item)); ?&amp;gt;
&amp;lt;h1 class=&amp;quot;article-title&amp;quot;&amp;gt;
...
&lt;/pre&gt;
&lt;h2&gt;Personalizzazione del form&lt;/h2&gt;

&lt;p&gt;Già a questo punto le operazioni di inserimento e modifica di un articolo funzionerebbero grazie al codice autogenerato presente nei metodi di gestione delle azioni. Bisogna però personalizzare l'aspetto del form nel metodo &lt;strong&gt;configure()&lt;/strong&gt; della classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraArticleForm.class.php"&gt;LyraArticleForm&lt;/a&gt;. Non riporto per intero il codice perché si tratta di operazioni semplici ed esempi analoghi di configurazione di un form si trovano nel codice di Jobeet: vengono rimossi dalla visualizzazione tutti i campi che non devono essere gestiti dall'utente, vengono rinominate le etichette di campi utilizzando costanti stringa da tradurre e viene modificato l'ordine di alcuni campi.&lt;/p&gt;

&lt;p&gt;L'unica parte più complessa è la generazione delle liste di selezione attraverso le quali si scelgono le etichette da assegnare all'articolo. Per poter dare anche solo qualche cenno di  spiegazione di questa parte, occorre prima capire l'utilizzo di &lt;strong&gt;Cataloghi&lt;/strong&gt; ed &lt;strong&gt;Etichette&lt;/strong&gt; per categorizzare i contenuti in Lyra. Ho già accennato qualcosa, ma arrivati a questo punto è necessaria una trattazione più esauriente che merita un post a parte, il prossimo.&lt;/p&gt;
&lt;p&gt;Tutte le modifiche sono incluse nella &lt;strong&gt;revisione 12&lt;/strong&gt;. Per allinearsi a questa revisione oltre al checkout della propria copia di lavoro locale è necessario eseguire i comandi&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;

&lt;p&gt;A questo punto scrivendo nella barra indirizzi del browser&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/frontend_dev.php/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;si apre la prima pagina con gli articoli dei dati di esempio. Visualizzando un articolo a pagina intera compare la barra di amministrazione: è già possibile modificare o inserire un articolo anche se il form è ancora abbastanza rudimentale. Bisogna ricordarsi di impostare sempre lo stato &lt;strong&gt;Pubblicato&lt;/strong&gt; e &lt;strong&gt;Prima pagina&lt;/strong&gt; perché senza backend e avendo nel frontend solo la prima pagina, questo è l'unico modo per vedere l'articolo inserito.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-7254299662585826563?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/L5_DIl_PdNp7AKWzWweG5gviG68/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/L5_DIl_PdNp7AKWzWweG5gviG68/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/L5_DIl_PdNp7AKWzWweG5gviG68/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/L5_DIl_PdNp7AKWzWweG5gviG68/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/_t-kSrpYJuY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/7254299662585826563/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-inserimento-e-modifica-articoli.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/7254299662585826563?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/7254299662585826563?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-inserimento-e-modifica-articoli.html" title="Lyra, inserimento e modifica articoli (frontend)" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0IEQXw7eyp7ImA9WxNWFUQ.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6031136973554302424</id><published>2009-10-15T09:25:00.002+02:00</published><updated>2009-10-15T09:25:00.203+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-15T09:25:00.203+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, gestione metatag articolo</title><content type="html">&lt;p&gt;In Lyra come si può notare dallo schema del database (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/config/doctrine/schema.yml"&gt;schema.yml&lt;/a&gt;, LyraArticle),  esistono campi per impostare su ogni articolo il contenuto di alcuni &lt;strong&gt;metatag&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;meta description&lt;/li&gt;
&lt;li&gt;meta keywords&lt;/li&gt;
&lt;li&gt;meta robots&lt;/li&gt;
&lt;li&gt;meta title&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Della visualizzazione dei metatag si occupano apposite funzioni helper richiamate nel layout.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/templates/layout.php
...
&amp;lt;head&amp;gt;
  &amp;lt;?php include_http_metas() ?&amp;gt;
  &amp;lt;?php include_metas() ?&amp;gt;
  &amp;lt;?php include_title() ?&amp;gt;
  ...
&amp;lt;/head&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;I valori dei metatag si impostano nell'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt; utilizzando metodi dell'oggetto response (&lt;strong&gt;sfWebResponse&lt;/strong&gt;) la cui istanza otteniamo con il metodo &lt;strong&gt;getResponse()&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

public function executeShow(sfWebRequest $request)
{
  ...
  $this-&amp;gt;item-&amp;gt;setMetaTags($this-&amp;gt;getResponse());
}
&lt;/pre&gt;
Il metodo &lt;strong&gt;setMetaTags()&lt;/strong&gt; è implementato nel modello.
&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
  ...
  public function setMetaTags(sfWebResponse $response)
  {
    $mt = $this-&amp;gt;getMetaTitle();
    if(!$mt) {
      $mt = $this-&amp;gt;getTitle();
    }
    $response-&amp;gt;setTitle($mt);

    if($mt = $this-&amp;gt;getMetaDescr()) {
        $response-&amp;gt;addMeta('description', $mt);
    }
    if($mt = $this-&amp;gt;getMetaKeys()) {
        $response-&amp;gt;addMeta('keywords', $mt);
    }
    if($mt = $this-&amp;gt;getMetaRobots()) {
        $response-&amp;gt;addMeta('robots', $mt);
    }
  }
...
}
&lt;/pre&gt;
&lt;p&gt;Come si può notare se al momento della creazione dell'articolo non si è inserito alcun valore nel campo &lt;strong&gt;meta_title&lt;/strong&gt;, si utilizza come meta-titolo il titolo dell'articolo, che non può essere vuoto in quanto campo obbligatorio. Gli altri metatag possono invece essere tranquillamente omessi. &lt;/p&gt;

&lt;p&gt;Queste poche modifiche sono incluse nella &lt;strong&gt;revisione 11&lt;/strong&gt; su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6031136973554302424?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/SNwJlv2c9oI6QHIgL48-GaM7h_Q/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/SNwJlv2c9oI6QHIgL48-GaM7h_Q/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/SNwJlv2c9oI6QHIgL48-GaM7h_Q/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/SNwJlv2c9oI6QHIgL48-GaM7h_Q/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/XmjGA9dE0pc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6031136973554302424/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-gestione-metatag-articolo.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6031136973554302424?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6031136973554302424?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-gestione-metatag-articolo.html" title="Lyra, gestione metatag articolo" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;A0QCQXo6fCp7ImA9WxNWFU0.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-9217448189198723671</id><published>2009-10-14T10:36:00.000+02:00</published><updated>2009-10-14T10:36:00.414+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-14T10:36:00.414+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, visualizzazione articolo</title><content type="html">&lt;p&gt;Dopo aver implementato, almeno a grandi linee, l'azione &lt;strong&gt;index&lt;/strong&gt; per la visualizzazione dell'elenco articoli in prima pagina, passiamo all'azione &lt;strong&gt;show&lt;/strong&gt; che serve a visualizzare un singolo articolo a tutta pagina. Il procedimento è del tutto analogo a quello visto in precedenza per cui non mi dilungo molto.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeShow(sfWebRequest $request)
  {
    $this-&amp;gt;item = Doctrine::getTable('LyraArticle')
      -&amp;gt;find($request-&amp;gt;getParameter('id'));
    $this-&amp;gt;forward404Unless($this-&amp;gt;item);
  }
...
&lt;/pre&gt;

&lt;p&gt;Il metodo &lt;strong&gt;find()&lt;/strong&gt; seleziona uno o più record in base alla chiave primaria. Come si è visto nell'articolo precedente quando si sono generati i link 'leggi tutto' la rotta per la pagina di un articolo ha il formato&lt;/p&gt;

&lt;p&gt;&lt;code&gt;article/show/id/x&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;dove &lt;em&gt;x&lt;/em&gt; è il valore del campo &lt;strong&gt;ID&lt;/strong&gt; chiave primaria del record; questo valore viene passato nella richiesta e letto con il metodo &lt;strong&gt;getParameter()&lt;/strong&gt; della classe &lt;strong&gt;sfWebRequest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Il metodo &lt;strong&gt;forward404Unless()&lt;/strong&gt; genera un errore 404 se l'argomento che gli viene passato è &lt;em&gt;false&lt;/em&gt;, cioè se il metodo &lt;strong&gt;find()&lt;/strong&gt; non ha trovato un record con chiave corrispondente.&lt;/p&gt;

&lt;p&gt;Passiamo al template corrispondente all'azione &lt;strong&gt;show&lt;/strong&gt;: il codice generato automaticamente dal framework è sostituito dal seguente:&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/showSuccess.php

&amp;lt;h1 class=&amp;quot;article-title&amp;quot;&amp;gt;
  &amp;lt;?php echo $item-&amp;gt;getTitle() ?&amp;gt;
&amp;lt;/h1&amp;gt;
&amp;lt;?php if($item-&amp;gt;getSubtitle()): ?&amp;gt;
  &amp;lt;div class=&amp;quot;article-subtitle&amp;quot;&amp;gt;
    &amp;lt;?php echo $item-&amp;gt;getSubtitle() ?&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;?php endif ?&amp;gt;
&amp;lt;div class=&amp;quot;article-date&amp;quot;&amp;gt;
  &amp;lt;?php echo $item-&amp;gt;getCreatedAt() ?&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;article-content&amp;quot;&amp;gt;
  &amp;lt;?php echo $item-&amp;gt;getContent(ESC_RAW) ?&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$item&lt;/strong&gt; è l'articolo che deve essere visualizzato, un oggetto istanza della classe &lt;strong&gt;LyraArticle&lt;/strong&gt;. I valori dei campi si ottengono con i metodi già incontrati.&lt;/p&gt;

&lt;p&gt;Esiste un campo &lt;strong&gt;sottotitolo&lt;/strong&gt; che ho incluso solo nella visualizzazione dell'articolo a tutta pagina, se lo si vuole visualizzare anche quando gli articoli sono mostrati con sommario e 'leggi tutto' basta aggiungere il blocco nel partial &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/frontend/modules/article/templates/_list.php"&gt;_list.php&lt;/a&gt;.&lt;/p&gt;

Oggi si lavora poco, con queste modifiche siamo &lt;strong&gt;alla revisione 10&lt;/strong&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-9217448189198723671?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/P3bcVkWWJUM5zdjU-uSVuQRqsv4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/P3bcVkWWJUM5zdjU-uSVuQRqsv4/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/P3bcVkWWJUM5zdjU-uSVuQRqsv4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/P3bcVkWWJUM5zdjU-uSVuQRqsv4/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/4495JGMEwvU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/9217448189198723671/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-visualizzazione-articolo.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/9217448189198723671?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/9217448189198723671?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-visualizzazione-articolo.html" title="Lyra, visualizzazione articolo" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkcGRXg4fip7ImA9WxNWE0k.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1813975618870651794</id><published>2009-10-12T12:38:00.000+02:00</published><updated>2009-10-12T12:40:24.636+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-12T12:40:24.636+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, azioni del modulo article</title><content type="html">&lt;p&gt;Fino a questo punto abbiamo modificato il &lt;strong&gt;layout globale&lt;/strong&gt; dell'applicazione frontend di Lyra. È il momento di iniziare a scrivere il codice che dovrà gestire le varie azioni del modulo &lt;strong&gt;article&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quando abbiamo creato il modulo &lt;strong&gt;article&lt;/strong&gt; con il comando &lt;strong&gt;doctrine:generate-module&lt;/strong&gt; è stata creata una classe &lt;strong&gt;articleActions&lt;/strong&gt; in &lt;em&gt;apps/frontend/modules/article/actions/actions.class.php&lt;/em&gt; e sono stati generati alcuni metodi per processare un certo numero di azioni base: tra queste, &lt;strong&gt;index&lt;/strong&gt; viene di regola utilizzata per mostrare un elenco di record, nel nostro caso l'elenco degli articoli pubblicati sulla prima pagina del sito.&lt;/p&gt;

&lt;h2&gt;La prima pagina in Lyra&lt;/h2&gt;

&lt;p&gt;In questa fase di sviluppo iniziale la prima pagina del sito è costituita semplicemente da un elenco di articoli ordinati per data decrescente in "stile blog": titolo, data, autore (quando ci sarà perché non abbiamo ancora la gestione utenti), sommario o testo introduttivo ed eventuale link 'leggi tutto'.&lt;/p&gt;
&lt;p&gt;Come si è visto nell'articolo precedente, l'azione &lt;strong&gt;index&lt;/strong&gt; è processata da un metodo &lt;strong&gt;executeIndex()&lt;/strong&gt; della classe &lt;strong&gt;articleActions.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
   public function executeIndex(sfWebRequest $request)
   {
      $this-&amp;gt;items = Doctrine::getTable('LyraArticle')-&amp;gt;getFrontPageItems();
   }
  ...
}
&lt;/pre&gt;

&lt;p&gt;Per ottenere l'elenco degli articoli in prima pagina è necessaria ovviamente un'interrogazione del database e, per le considerazioni fatte la volta scorsa, questo è il compito di un oggetto Model. Otteniamo quindi un'istanza della classe &lt;strong&gt;LyraArticleTable&lt;/strong&gt; e ne invochiamo il metodo &lt;strong&gt;getFrontPageItems()&lt;/strong&gt; che scriveremo tra poco.&lt;/p&gt;

&lt;p&gt;Il risultato è un array di record che viene salvato come proprietà &lt;em&gt;items&lt;/em&gt;. Quando creiamo una proprietà in questo modo nel codice di un'azione il valore assegnato sarà visibile dalla view (&lt;strong&gt;template&lt;/strong&gt;) come variabile.&lt;/p&gt;

&lt;p&gt;Si è visto (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-generazione-del-modello.html"&gt;symfony, generazione del Modello&lt;/a&gt;) che in &lt;em&gt;lib/model/doctrine&lt;/em&gt; esistono due classi per ogni tabella dello schema. Per quanto riguarda gli articoli:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;LyraArticleTable che rappresenta la tabella;&lt;/li&gt;
&lt;li&gt;LyraArticle che rappresenta un record della tabella.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

Di solito la prima contiene i metodi che ritornano i risultati delle interrogazioni sulla tabella, è lì che implementeremo &lt;strong&gt;getFrontPageItems()&lt;/strong&gt;.

&lt;pre&gt;
lib/model/doctrine/LyraArticleTable.class.php

class LyraArticleTable extends Doctrine_Table
{
  public function getFrontPageItems()
  {
    $q = $this-&amp;gt;getActiveItemsQuery()
      -&amp;gt;andWhere('a.is_featured = ?', true);
    return $q-&amp;gt;execute();
  }
  
  public function getActiveItems()
  {
    $q = $this-&amp;gt;getActiveItemsQuery();
    return $q-&amp;gt;execute();
  }

  public function getActiveItemsQuery()
  {
    return $this-&amp;gt;createQuery('a')
      -&amp;gt;where('a.is_active = ?', true)
      -&amp;gt;addOrderBy('a.is_sticky DESC, a.created_at DESC');
  }
}
&lt;/pre&gt;

&lt;p&gt;Il metodo &lt;strong&gt;getActiveItemsQuery()&lt;/strong&gt; ritorna una query per la selezione degli articoli pubblicati (&lt;strong&gt;is_active&lt;/strong&gt; è &lt;em&gt;true&lt;/em&gt;) ordinati secondo il criterio di ordinamento predefinito (&lt;strong&gt;is_sticky&lt;/strong&gt; a &lt;em&gt;true&lt;/em&gt; indica che l'articolo sta sempre in testa alla lista).&lt;/p&gt;

&lt;p&gt;Il metodo &lt;strong&gt;getFrontPageItems()&lt;/strong&gt; aggiunge un proprio criterio di selezione (&lt;strong&gt;is_featured&lt;/strong&gt; è &lt;em&gt;true&lt;/em&gt; per gli articoli da visualizzare in prima pagina) ed esegue la query.&lt;/p&gt;

&lt;p&gt;Le query sono scritte utilizzando &lt;strong&gt;Doctrine Query Language&lt;/strong&gt; (DQL) su cui si possono trovare tutte le informazioni nella documentazione ufficiale. Faccio notare soltanto:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;al metodo &lt;strong&gt;createQuery()&lt;/strong&gt; basta passare un alias ('a'), non serve sia specificato il nome della tabella in quanto implicitamente è quello della tabella di riferimento della classe (nel nostro caso &lt;em&gt;articles&lt;/em&gt;);&lt;/li&gt;
&lt;li&gt;il '?' nelle condizioni &lt;em&gt;where&lt;/em&gt; funge da segnaposto per un parametro che viene passato come argomento successivo;&lt;/li&gt;
&lt;li&gt;Il risultato dell'esecuzione della query con il metodo &lt;strong&gt;execute()&lt;/strong&gt; è un array di oggetti istanze della classe &lt;strong&gt;LyraArticle&lt;/strong&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Resta da creare il template. La prima considerazione da fare è che il formato di visualizzazione degli articoli in prima pagina (titolo, sommario con link 'leggi tutto') sarà utilizzato anche in altre parti del sito, ad esempio nelle pagine che mostrano gli articoli appartenenti ad una categoria. Conviene da subito mettersi in condizione di evitare duplicazioni di codice: quindi per prima cosa creiamo un &lt;strong&gt;partial&lt;/strong&gt;, cioè un blocco di codice che può essere richiamato da più di un template.  &lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/_list.php

&amp;lt;?php foreach ($items as $item): ?&amp;gt;
  &amp;lt;h2 class=&amp;quot;article-title&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to($item-&amp;gt;getTitle(), 'article/show?id='.$item-&amp;gt;getId())?&amp;gt;
  &amp;lt;/h2&amp;gt;
  &amp;lt;div class=&amp;quot;article-date&amp;quot;&amp;gt;
    &amp;lt;?php echo $item-&amp;gt;getCreatedAt() ?&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;article-summary&amp;quot;&amp;gt;
    &amp;lt;?php
    echo $item-&amp;gt;getSummary(ESC_RAW);
    if($item-&amp;gt;showReadmore()): ?&amp;gt;
      &amp;lt;span class=&amp;quot;article-readmore&amp;quot;&amp;gt;
      &amp;lt;?php echo link_to(__('LINK_READMORE'), 'article/show?id='.$item-&amp;gt;getId(), array('title'=&amp;gt;$item-&amp;gt;getTitle()))?&amp;gt;
      &amp;lt;/span&amp;gt;
    &amp;lt;?php endif ?&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;article-separator&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;?php endforeach; ?&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Un partial ha sempre un nome di file che inizia con il carattere sottolineato:&lt;em&gt;_list.php&lt;/em&gt;  viene incluso nel template &lt;em&gt;indexSuccess.php&lt;/em&gt; con questa sintassi:&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/indexSuccess.php

&amp;lt;?php include_partial('article/list', array('items'=&amp;gt;$items)); ?&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Alcune cose da notare. L'helper &lt;strong&gt;link_to()&lt;/strong&gt; serve a generare un link HTML: il primo parametro è il testo da usare per l'ancora, il secondo la rotta in base alla quale generare la URL per il link; il formato della rotta (modulo/azione/parametri) è quello già visto nell'articolo precedente. L'azione &lt;strong&gt;show&lt;/strong&gt; servirà a mostrare l'articolo a tutta pagina, i parametri (nel nostro caso solo l'id dell'articolo da visualizzare) sono passati nel formato usato per una query string: coppie chiave=valore, separate dal carattere '&amp;amp;' se sono più di una.&lt;/p&gt;

&lt;p&gt;Come accennato, il valore della proprietà &lt;em&gt;items&lt;/em&gt; del controller (assegnato con &lt;strong&gt;$this-&gt;items&lt;/strong&gt; in &lt;strong&gt;executeIndex()&lt;/strong&gt;), viene visto come variabile (&lt;strong&gt;$items&lt;/strong&gt;) nel template e passato al partial con lo stesso identificatore &lt;em&gt;items&lt;/em&gt; (dal codice array('items'=&gt;$items)).&lt;/p&gt;

&lt;p&gt;Nel partial il contenuto dei singoli campi di ogni record è letto tramite metodi getter. Il parametro &lt;strong&gt;ESC_RAW&lt;/strong&gt; si usa quando si vuole il contenuto del campo senza mascheramento delle entità HTML, questo è necessario quando il campo (nel nostro caso il sommario dell'articolo) può contenere codice HTML.&lt;/p&gt;

&lt;p&gt;Richiamo anche l'attenzione sul modo in cui viene creata l'ancora per il link 'leggi tutto': si utilizza una costante simbolica che sarà tradotta in base ai file di lingua. Questo modo di procedere è diverso da quello del tutorial ufficiale (&lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/19"&gt;Internazionalizzazione e Localizzazione&lt;/a&gt;) dove si utilizza una lingua predefinita nei template e poi si generano i file di traduzione solo per le lingue diverse da quella predefinita.&lt;/p&gt;

&lt;p&gt;Io preferisco utilizzare delle costanti nei template per poi generare i file di traduzione per tutte le lingue (vedremo in seguito come). In questo modo la personalizzazione dell'interfaccia è più semplice: ad esempio se si preferisce avere non 'leggi tutto', ma 'Continua ...' o altro non sarà necessario modificare il sorgente dell'applicazione, ma solo il file di traduzione. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;__()&lt;/strong&gt; è un helper che serve appunto a tradurre una stringa in diversi lingue: perché possa funzionare è necessario che le funzioni multilingua siano attivate nel file delle impostazioni.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/config/settings.yml
...
all:
  .settings:
    standard_helpers: [Partial, Cache, I18N]
    i18n: on
    default_culture: it
...
&lt;/pre&gt;
&lt;p&gt;Ho eseguito il commit della &lt;strong&gt;revisione 9&lt;/strong&gt; su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;. Se si è configurato un proprio ambiente di sviluppo secondo le indicazioni date negli articoli precedenti, si possono vedere i risultati del lavoro facendo il checkout della revisione e digitando nella barra indirizzi del browser&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/frontend_dev.php/article&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Poiché &lt;strong&gt;index&lt;/strong&gt; è l'azione predefinita, non serve indicarla nella URL. L'indirizzo seguente produrrebbe lo stesso risultato&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/frontend_dev.php/article/index&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Visto che non abbiamo generato i file delle traduzioni, come ancora del link leggi tutto sarà visualizzata la costante.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1813975618870651794?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/rilr0lMP0rTZd3wyokcewuEx5zA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rilr0lMP0rTZd3wyokcewuEx5zA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/rilr0lMP0rTZd3wyokcewuEx5zA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rilr0lMP0rTZd3wyokcewuEx5zA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/oei-a4btyNg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1813975618870651794/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-azioni-del-modulo-article.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1813975618870651794?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1813975618870651794?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/lyra-azioni-del-modulo-article.html" title="Lyra, azioni del modulo article" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C04CR3Y6eip7ImA9WxNXGUQ.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8646134290565996505</id><published>2009-10-08T10:50:00.000+02:00</published><updated>2009-10-08T10:52:46.812+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-08T10:52:46.812+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, struttura progetto</title><content type="html">&lt;p&gt;Prima di proseguire penso sia utile fare il punto su quanto sviluppato fino ad ora. Questo ci consente tra l'altro di spendere qualche parola su come è strutturato un progetto &lt;strong&gt;symfony&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Il progetto&lt;/h2&gt;

&lt;p&gt;La prima fase dello sviluppo (a parte &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/installazione-del-framework-symfony.html"&gt;l'installazione del framework&lt;/a&gt; che però si risolve semplicemente in un'operazione di copia di file) è stata la generazione della struttura del progetto. È quello che abbiamo fatto in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html"&gt;Creazione progetto e applicazione&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Le applicazioni&lt;/h2&gt;

&lt;p&gt;Il progetto è diviso in una o più &lt;strong&gt;applicazioni&lt;/strong&gt;. Per Lyra abbiamo iniziato lo sviluppo dell'applicazione di &lt;strong&gt;frontend&lt;/strong&gt;, esisterà anche un'applicazione di &lt;strong&gt;backend&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Model View Controller&lt;/h2&gt;

&lt;p&gt;Un progetto symfony è strutturato secondo la logica del &lt;em&gt;design pattern&lt;/em&gt; &lt;strong&gt;Model View Controller&lt;/strong&gt; in base al quale un'applicazione può essere suddivisa in tre parti fondamentali.&lt;/p&gt;

&lt;p&gt;La parte &lt;strong&gt;Model&lt;/strong&gt; offre un'interfaccia verso il database e determina la logica applicativa. In symfony è costituita da una serie di classi in &lt;em&gt;lib/model&lt;/em&gt;: alcune di queste sono generate dal framework in base alle informazioni contenute nel file &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt; (che abbiamo creato in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-schema-database.html"&gt;Creazione schema database&lt;/a&gt;) e non devono essere modificate, altre possono essere personalizzate secondo le esigenze specifiche. Ne abbiamo parlato nell'articolo &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-generazione-del-modello.html"&gt;Generazione del Modello&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In un progetto come Lyra queste classi offrono i metodi per eseguire interrogazioni del database e compiere le operazioni di creazione, modifica, validazione e salvataggio dei dati.&lt;/p&gt;

&lt;p&gt;La parte &lt;strong&gt;View&lt;/strong&gt; si occupa della rappresentazione dei dati. In symfony è costituita da una serie di &lt;strong&gt;template&lt;/strong&gt; scritti in PHP + HTML che si integrano con il &lt;strong&gt;layout&lt;/strong&gt; globale di cui abbiamo parlato nell'articolo &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-modulo-frontend-e.html"&gt; creazione modulo frontend e layout&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La parte &lt;strong&gt;Controller&lt;/strong&gt; elabora le richieste dell'utente. A seconda della richiesta specifica, se necessario viene invocato un metodo di un oggetto &lt;strong&gt;Model&lt;/strong&gt; e restituita una risposta. La risposta tipica, anche se non l'unica possibile, è un documento HTML il cui aspetto è determinato dal layout e da un template. In symfony il Controller è costituito da un &lt;strong&gt;front controller&lt;/strong&gt; ed una serie di &lt;strong&gt;action&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Front controller&lt;/h2&gt;

&lt;p&gt;Sono generati dal framework e suddivisi per &lt;strong&gt;applicazione&lt;/strong&gt; ed &lt;strong&gt;ambiente&lt;/strong&gt;; di ambienti non ho avuto occasione di parlare, per semplificare con riguardo a Lyra, abbiamo per il momento un ambiente di &lt;strong&gt;sviluppo&lt;/strong&gt; e un ambiente di &lt;strong&gt;produzione&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Il front controller per l'ambiente di produzione della prima applicazione generata nel progetto (in Lyra, come di solito accade, il frontend) è chiamato &lt;em&gt;index.php&lt;/em&gt;, gli altri front controller prendono il nome dell'applicazione più, per tutti gli ambienti diversi da produzione, un suffisso determinato in base all'ambiente.&lt;/p&gt;

&lt;p&gt;I front controller costituiscono il punto di ingresso di ogni applicazione e devono poter essere invocati direttamente dal browser dell'utente: sono quindi collocati nella cartella &lt;em&gt;web&lt;/em&gt; che come abbiamo visto (configurazione host virtuale in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html"&gt;Creazione progetto e applicazione&lt;/a&gt;) è impostata come &lt;strong&gt;Document Root&lt;/strong&gt; di Apache.&lt;/p&gt;
&lt;p&gt;In Lyra questa cartella al momento contiene:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;strong&gt;index.php&lt;/strong&gt; - front controller di produzione dell'applicazione frontend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;frontend_dev.php&lt;/strong&gt; - front controller di sviluppo dell'applicazione frontend.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;In seguito saranno generati anche:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;strong&gt;backend.php&lt;/strong&gt; - front controller di produzione dell'applicazione backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;backend_dev.php&lt;/strong&gt; - front controller di sviluppo dell'applicazione backend.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Aprendo con un editor uno dei front controller notiamo che contiene poche righe di codice standard, che di regola non è necessario modificare, la cui funzione è quella di dare il via ad un processo che determina l'azione richiesta dall'utente.&lt;/p&gt;

&lt;h2&gt;Actions&lt;/h2&gt;

&lt;p&gt;Sono raggruppate in &lt;strong&gt;moduli&lt;/strong&gt;. Ad ogni &lt;strong&gt;azione&lt;/strong&gt; corrisponde di regola un &lt;strong&gt;template&lt;/strong&gt; utilizzato per la risposta.&lt;/p&gt;

&lt;p&gt;Ad esempio quando abbiamo creato il modulo &lt;strong&gt;article&lt;/strong&gt; per il &lt;strong&gt;frontend&lt;/strong&gt; di Lyra (vedi &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-modulo-frontend-e.html"&gt;creazione modulo frontend e layout&lt;/a&gt;) è stato generato un file &lt;em&gt;actions.class.php&lt;/em&gt; in &lt;em&gt;apps/frontend/modules/article/actions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Se apriamo il file noteremo una serie di metodi pubblici il cui nome inizia per &lt;strong&gt;execute&lt;/strong&gt;: ne esiste uno per ogni azione elaborata dal controller. In &lt;em&gt;apps/frontend/modules/article/templates&lt;/em&gt; troviamo i &lt;strong&gt;template&lt;/strong&gt; corrispondenti.&lt;/p&gt;

&lt;p&gt;Ad esempio l'azione &lt;strong&gt;show&lt;/strong&gt; è processata da&lt;/p&gt;
&lt;pre&gt;
class articleActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
      ...
  }
}
&lt;/pre&gt;
&lt;p&gt;e il template corrispondente è &lt;em&gt;showSuccess.php&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;URL e routing&lt;/h2&gt;

&lt;p&gt;Gli utenti interagiscono con una applicazione web in diversi modi. Ad esempio:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;digitando un indirizzo nel browser;&lt;/li&gt;
&lt;li&gt;navigando un link;&lt;/li&gt;
&lt;li&gt;inviando dati attraverso un form;&lt;/li&gt;
&lt;li&gt;iniziando una richiesta AJAX.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Ognuna di queste modalità implica ricevere o inviare dati da/a una determinata URL. Quindi è attraverso la URL che si può determinare l'azione che l'utente richiede all'applicazione. In symfony le URL seguono un formato standard predefinito che come vedremo può però essere personalizzato:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;front_controller.php/modulo/azione/parametri&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;dove i parametri sono costituiti da coppie di chiavi/valori separate da '/'. Poniamo di digitare il seguente indirizzo nel browser:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/index.php/article/show/id/1&lt;/code&gt;&lt;/p&gt;
 
&lt;p&gt;Assumendo che &lt;em&gt;index.php&lt;/em&gt; sia il front controller dell'applicazione frontend nell'ambiente produzione (abbiamo visto sopra che di solito è così), stiamo eseguendo l'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt;. Il framework eseguirà il metodo &lt;strong&gt;executeShow()&lt;/strong&gt; della classe &lt;strong&gt;articleActions&lt;/strong&gt; (&lt;em&gt;apps/frontend/modules/article/actions/actions.class.php&lt;/em&gt;), all'interno del quale potremo accedere ai parametri della richiesta in questo modo:&lt;/p&gt;
&lt;pre&gt;
class articleActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
      $id = $request-&amp;gt;getParameter('id');
      ...
  }
}
&lt;/pre&gt; 
&lt;p&gt;Se la richiesta ha successo la risposta sarà data dal template &lt;strong&gt;showSuccess.php&lt;/strong&gt; in &lt;em&gt;apps/frontend/modules/article/templates&lt;/em&gt;, che ricordo verrà incorporato nel layout (&lt;em&gt;apps/frontend/templates/layout.php&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Quindi una URL costituisce una rotta verso una determinata azione di un modulo. Le informazioni relative alle diverse rotte utilizzabili dall'applicazione si trovano in file in formato &lt;strong&gt;YAML&lt;/strong&gt; (&lt;em&gt;apps/frontend/config/routing.yml&lt;/em&gt; e i file omologhi per il backend ed eventuali altre applicazioni del progetto) che avremo modo di modificare più volte nel corso dello sviluppo.&lt;/p&gt;

&lt;p&gt;La prossima volta si riprenderà il lavoro sull'applicazione, un quadro riassuntivo mi è sembrato utile per capire le operazioni che svolgeremo da ora in poi.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8646134290565996505?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/KRVlYlcn80lRt6-Dv0uMI0005BE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KRVlYlcn80lRt6-Dv0uMI0005BE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/KRVlYlcn80lRt6-Dv0uMI0005BE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/KRVlYlcn80lRt6-Dv0uMI0005BE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/4biSXfh-cdE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8646134290565996505/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-struttura-progetto.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8646134290565996505?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8646134290565996505?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-struttura-progetto.html" title="symfony, struttura progetto" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;Ak8MQnk9fCp7ImA9WxNXGE8.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4763999366289721450</id><published>2009-10-06T13:33:00.000+02:00</published><updated>2009-10-06T13:34:43.764+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-06T13:34:43.764+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, creazione modulo frontend e layout</title><content type="html">&lt;p&gt;Con i dati di esempio inseriti nel database possiamo creare il &lt;strong&gt;modulo&lt;/strong&gt; per la visualizzazione degli articoli nel frontend. In symfony un'applicazione è suddivisa in moduli ed ogni modulo fornisce un determinato insieme di funzionalità. In particolare in &lt;strong&gt;Lyra&lt;/strong&gt; avremo un modulo per ogni tipo di contenuto gestito dal cms. Il modulo &lt;strong&gt;article&lt;/strong&gt; sarà quindi il primo che creeremo&lt;/p&gt;

&lt;h2&gt;Generazione di un modulo&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:generate-module --with-show  --non-verbose-templates frontend article LyraArticle&lt;/pre&gt;

&lt;p&gt;A patto che si sia configurato un host virtuale per il progetto come descritto in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html"&gt;symfony, creazione progetto e applicazione&lt;/a&gt;, si può già vedere qualcosa nel browser con&lt;/p&gt;

&lt;code&gt;http://lyra/frontend_dev.php/article&lt;/code&gt;

&lt;p&gt;L'aspetto non è certo gradevole da vedere, gli articoli inclusi nei dati di esempio sono visualizzati in un formato tabellare senza alcuna particolare formattazione. Questo perché non abbiamo configurato alcun &lt;strong&gt;layout&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Personalizzazione del layout&lt;/h2&gt;

&lt;p&gt;In symfony ogni pagina HTML è generata a partire da una sorta di template globale, il &lt;strong&gt;layout&lt;/strong&gt;, che ne determina la struttura (aspetto e dimensioni di header, footer, area principale e colonne) quelle parti di codice cioè che sono comuni a più pagine.&lt;/p&gt;

&lt;p&gt;Il contenuto specifico di ogni pagina è invece generato da &lt;strong&gt;template&lt;/strong&gt; che vengono incorporati nel layout. Questa figura tratta dalla &lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/04"&gt;documentazione ufficiale&lt;/a&gt; rende bene l'idea del meccanismo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SsoX6hG1RYI/AAAAAAAAAGY/nZtO1hfOQOY/s1600-h/layout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 88px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SsoX6hG1RYI/AAAAAAAAAGY/nZtO1hfOQOY/s320/layout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5389146198189098370" /&gt;&lt;/a&gt;
&lt;p&gt;Per il layout ho adattato un template gratuito dalla struttura molto semplice.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/templates/layout.php

&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD XHTML 1.0 Strict//EN&amp;quot;
   &amp;quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&amp;quot;&amp;gt;
&amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;?php include_http_metas() ?&amp;gt;
    &amp;lt;?php include_metas() ?&amp;gt;
    &amp;lt;?php include_title() ?&amp;gt;
    &amp;lt;?php use_stylesheet('main.css') ?&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;wrapper&amp;quot;&amp;gt;
      &amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt;
        &amp;lt;div id=&amp;quot;top&amp;quot;&amp;gt;
          &amp;lt;div id=&amp;quot;logo&amp;quot;&amp;gt;
            &amp;lt;h1&amp;gt;Logo&amp;lt;/h1&amp;gt;
            &amp;lt;h4&amp;gt;Slogan&amp;lt;/h4&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div id=&amp;quot;links&amp;quot;&amp;gt;
            &amp;lt;ul&amp;gt;
              &amp;lt;li&amp;gt;&amp;lt;?php echo link_to('Home','@homepage');?&amp;gt;&amp;lt;/li&amp;gt;
              &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Articoli&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
              &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Contattaci&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
          &amp;lt;h3&amp;gt;Titolo pagina&amp;lt;/h3&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&amp;quot;contentarea&amp;quot;&amp;gt;
          &amp;lt;div id=&amp;quot;leftbar&amp;quot;&amp;gt;
            &amp;lt;?php if ($sf_user-&amp;gt;hasFlash('notice')): ?&amp;gt;
            &amp;lt;div class=&amp;quot;flash_notice&amp;quot;&amp;gt;
              &amp;lt;?php echo __($sf_user-&amp;gt;getFlash('notice')) ?&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;?php endif; ?&amp;gt;

            &amp;lt;?php if ($sf_user-&amp;gt;hasFlash('error')): ?&amp;gt;
            &amp;lt;div class=&amp;quot;flash_error&amp;quot;&amp;gt;
              &amp;lt;?php echo __($sf_user-&amp;gt;getFlash('error')) ?&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;?php endif; ?&amp;gt;
            &amp;lt;?php echo $sf_content ?&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div id=&amp;quot;rightbar&amp;quot;&amp;gt;
            &amp;lt;h4&amp;gt;Menu&amp;lt;/h4&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&amp;quot;footer&amp;quot;&amp;gt;
          &amp;lt;div id=&amp;quot;lyra-powered&amp;quot;&amp;gt;
            &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Powered by Lyra&amp;lt;/a&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div id=&amp;quot;validator&amp;quot;&amp;gt;
          &amp;lt;p&amp;gt;Valid &amp;lt;a href=&amp;quot;http://validator.w3.org/check?uri=referer&amp;quot;&amp;gt;XHTML&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Credo si veda chiaramente che questa è la 'cornice' della pagina. I contenuti sono inseriti da queste istruzioni&lt;/p&gt;

&lt;pre&gt;
&amp;lt;?php echo $sf_content ?&amp;gt;
&lt;/pre&gt;

&lt;p&gt;include il contenuto principale della pagina (corpo dell'articolo, lista articoli sulla pagina di una categoria e simili) che come vedremo è generato da una &lt;strong&gt;action&lt;/strong&gt; e dal relativo &lt;strong&gt;template&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
&amp;lt;?php include_http_metas() ?&amp;gt;
&amp;lt;?php include_metas() ?&amp;gt;
&amp;lt;?php include_title() ?&amp;gt;
&lt;/pre&gt;
&lt;p&gt;includono i &lt;strong&gt;metatag&lt;/strong&gt; e il contenuto del tag &amp;lt;TITLE&amp;gt;. Da notare anche&lt;/p&gt;

&lt;pre&gt;
&amp;lt;?php use_stylesheet('main.css') ?&amp;gt;
&lt;/pre&gt;
&lt;p&gt;che genera il tag &amp;lt;link&amp;gt; per includere un foglio di stile. Il file &lt;em&gt;main.css&lt;/em&gt; è stato inserito in &lt;a href="http://code.google.com/p/lyra-cms/source/browse/#svn/trunk/web/css"&gt;web/css&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Oltre al file del layout e al foglio di stile sono necessarie le immagini per lo sfondo e la testata: sono state inserite in &lt;a href="http://code.google.com/p/lyra-cms/source/browse/#svn/trunk/web/images"&gt;web/images&lt;/a&gt;. Delle altre parti del codice del layout avremo modo di parlare in futuro.&lt;/p&gt;

&lt;p&gt;Se  a questo punto digitiamo di nuovo nel browser&lt;/p&gt;
&lt;code&gt;http://lyra/frontend_dev.php/article&lt;/code&gt; 
&lt;p&gt;vediamo che l'aspetto della pagina rispecchia il layout anche se il contenuto (nell'esempio l'elenco articoli) è ancora in un formato tabellare 'grezzo'.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SsoZyAbJkAI/AAAAAAAAAGg/1wOJUIZa50s/s1600-h/lyra-layout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 143px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SsoZyAbJkAI/AAAAAAAAAGg/1wOJUIZa50s/s320/lyra-layout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5389148251000246274" /&gt;&lt;/a&gt;

&lt;p&gt;Questo perché non abbiamo ancora personalizzato il template della action. Cosa che faremo la prossima volta. A questo punto ho eseguito il commit della &lt;strong&gt;revisione 8&lt;/strong&gt; su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4763999366289721450?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/X6JIXrhRhKuSmfcD0gKpwOQNp7c/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/X6JIXrhRhKuSmfcD0gKpwOQNp7c/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/X6JIXrhRhKuSmfcD0gKpwOQNp7c/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/X6JIXrhRhKuSmfcD0gKpwOQNp7c/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/rkOi1jvtaTs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4763999366289721450/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-modulo-frontend-e.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4763999366289721450?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4763999366289721450?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-modulo-frontend-e.html" title="symfony, creazione modulo frontend e layout" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_unGpwtr1q-o/SsoX6hG1RYI/AAAAAAAAAGY/nZtO1hfOQOY/s72-c/layout.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0YAQXkyeyp7ImA9WxNXF0k.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5664646546631819586</id><published>2009-10-05T13:10:00.000+02:00</published><updated>2009-10-05T13:12:20.793+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-05T13:12:20.793+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, creazione dati di prova (fixtures)</title><content type="html">&lt;p&gt;La preparazione dei dati di esempio per lo sviluppo ed il test di un'applicazione è sempre una operazione abbastanza noiosa che fortunatamente symfony permette di semplificare.&lt;/p&gt;

&lt;p&gt;I dati di esempio (&lt;strong&gt;fixtures&lt;/strong&gt;) sono definiti in file in formato &lt;strong&gt;YAML&lt;/strong&gt; collocati in &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/fixtures/"&gt;data/fixtures&lt;/a&gt;. Il contenuto della cartella può essere sfogliato su Google Code, qui riporterò solo un estratto di alcuni dei file con qualche breve nota di commento.&lt;/p&gt;

&lt;h2&gt;Articoli&lt;/h2&gt;
&lt;pre&gt;
data/fixtures/articles.yml

LyraArticle:
  art1:
    title: Un articolo su PHP
    summary: Sommario primo articolo
    content: Contenuto primo articolo
    meta_title: Meta Title di articolo su PHP
    meta_descr: Meta Description di articolo su PHP
    is_featured: true
    is_active: true
    ArticleLabels: [Php,Intermedio]
  art2:
    title: Un articolo su Mootools
    ...
    ArticleLabels: [Mootools,Elementare]
&lt;/pre&gt;

&lt;p&gt;Da notare che:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;Non è necessario valorizzare le chiavi primarie dei record. Visto che sono definite in &lt;em&gt;schema.yml&lt;/em&gt; come &lt;strong&gt;ID&lt;/strong&gt; autoincrementanti, i valori vengono generati nel momento in cui il record viene inserito nel database. Quando è necessario riferirsi ad un record, ad esempio per impostare una relazione nel modo che vedremo tra poco, si utilizza un identificatore alfanumerico (in questo caso &lt;em&gt;art1&lt;/em&gt;, &lt;em&gt;art2&lt;/em&gt;);&lt;/li&gt;

&lt;li&gt;Non tutti i campi della tabella devono essere valorizzati e questo è abbastanza ovvio. Se si omette il valore di un campo sarà usato il valore di default, se definito nello schema.&lt;/li&gt;

&lt;li&gt;I record possono essere 'legati' utilizzando le relazioni tra tabelle definite nello schema. Ad esempio con &lt;strong&gt;ArticleLabels: [Php,Intermedio]&lt;/strong&gt; nel record &lt;em&gt;art1&lt;/em&gt; si 'assegnano' al record le etichette &lt;em&gt;Php&lt;/em&gt; e &lt;em&gt;Intermedio&lt;/em&gt; (identificatori di record nella tabella Etichette come vedremo).&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Commenti&lt;/h2&gt;
&lt;pre&gt;
data/fixtures/comments.yml

LyraComment:
  comm1:
    author_name: Pippo
    author_email: example@example.com
    author_url: http://www.example.com
    content: Articolo molto interessante
    is_active: true
    CommentArticle: art1
    ...
&lt;/pre&gt;

&lt;p&gt;Penso sia interessante anche in questo caso notare come i record della tabella commenti vengono legati ai relativi articoli. Non viene valorizzato direttamente il campo &lt;strong&gt;article_id&lt;/strong&gt;, visto che non conosciamo il valore delle chiavi primarie degli articoli che, come abbiamo detto, sono autogenerate; vengono invece assegnati gli identificatori dei record (&lt;em&gt;art1&lt;/em&gt;, &lt;em&gt;art2&lt;/em&gt;) sfruttando la relazione definita nello schema (&lt;strong&gt;CommentArticle&lt;/strong&gt;).&lt;/p&gt;
&lt;h2&gt;Etichette&lt;/h2&gt;
&lt;pre&gt;
data/fixtures/labels.yml

LyraCatalog:
  argomento:
    name: Argomento
    is_active: true
  livello:
    name: Livello
    is_active: true

LyraLabel:
  Rarg:
    name: Argomento
    is_active: true
    LabelCatalog: argomento
    children:
      Php:
        name: PHP
        title: Articoli su PHP
        is_active: true
        LabelCatalog: argomento
      Javascript:
        name: Javascript
        title: Articoli su Javascript
        is_active: true
        LabelCatalog: argomento
        children:
          Mootools:
            name: Mootools
            title: Articoli su Mootools
            is_active: true
            LabelCatalog: argomento
          jQuery:
            name: jQuery
            title: Articoli su jQuery
            is_active: true
            LabelCatalog: argomento
  Rliv:
    name: Livello
    is_active: true
    LabelCatalog: livello
    children:
      Elementare:
        name: Elementare
        is_active: true
        LabelCatalog: livello
      Intermedio:
        name: Intermedio
        is_active: true
        LabelCatalog: livello
      Avanzato:
        name: Avanzato
        is_active: true
        LabelCatalog: livello
&lt;/pre&gt;

&lt;p&gt;Come si vede ogni etichetta è legata ad un catalogo tramite la relazione &lt;strong&gt;LabelCatalog&lt;/strong&gt;; esistono inoltre relazioni gerarchiche tra le etichette. Ad esempio le etichette &lt;em&gt;Mootools&lt;/em&gt; e &lt;em&gt;jQuery&lt;/em&gt; sono figlie di &lt;em&gt;Javascript&lt;/em&gt;. Questo è possibile perché nello schema è stato definito un comportamento predefinito &lt;strong&gt;NestedSet&lt;/strong&gt; per la classe &lt;strong&gt;LyraLabel&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
config/doctrine/schema.yml
...
LyraLabel:
   tableName: labels
   actAs:
     ...
     NestedSet:
        hasManyRoots: true
        rootColumnName: root_id
...
&lt;/pre&gt;
&lt;p&gt;Non mi dilungo in questo momento su cosa siano i &lt;strong&gt;nested set&lt;/strong&gt; e rimando alla documentazione ufficiale di &lt;strong&gt;Doctrine&lt;/strong&gt;: in particolare &lt;a href="http://www.doctrine-project.org/documentation/manual/1_1/en/hierarchical-data"&gt;Hierarchical Data&lt;/a&gt; e &lt;a href="http://www.doctrine-project.org/documentation/manual/1_1/en/data-fixtures#fixtures-for-nested-sets"&gt;Fixtures for Nested Sets&lt;/a&gt;. In futuro ci sarà forse modo di approfondire il discorso.&lt;/p&gt;

&lt;p&gt;I dati di esempio si salvano nel database con il comando&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:data-load&lt;/pre&gt;

&lt;p&gt;Arrivati a questo punto conviene eseguire anche il comando&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-all-reload&lt;/pre&gt;

&lt;p&gt;In questo modo viene ricreata la struttura del database a partire dalle informazioni in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt;, vengono rigenerate le classi in &lt;em&gt;lib/model/doctrine&lt;/em&gt; e &lt;em&gt;lib/form/doctrine&lt;/em&gt; e infine vengono ricaricati i dati di esempio. Si riceverà una richiesta di conferma perché il contenuto del database viene cancellato e rimpiazzato dai dati di esempio.&lt;/p&gt;

&lt;p&gt;Ho eseguito il commit della &lt;strong&gt;revisione 7&lt;/strong&gt;. Ecco come allinearsi alla revisione corrente per chi non abbia avuto la pazienza di riprodurre sul proprio server di sviluppo tutte le operazioni descritte negli articoli fino a questo momento. Si assume un sistema Linux con cartella progetto &lt;em&gt;~/sfprojects/lyra&lt;/em&gt;, LAMP e client Subversion devono essere installati e si deve aver creato un database vuoto ed un utente con i privilegi sul database come spiegato in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-generazione-del-modello.html"&gt;symfony, generazione del Modello&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Da una finestra terminale digitare&lt;/p&gt;
&lt;pre&gt;cd ~/sfprojects/lyra
svn checkout -r7 http://lyra-cms.googlecode.com/svn/trunk/ .
&lt;/pre&gt;
&lt;p&gt;Importante mettere il punto finale. Una volta terminato il checkout, rimanendo sempre nella cartella del progetto&lt;/p&gt;
&lt;pre&gt;
gedit config/databases.yml
&lt;/pre&gt;
&lt;p&gt;Modificare il file con i propri dati di connessione. Se si è utilizzato &lt;em&gt;lyra&lt;/em&gt; come nome del proprio database e utente basta cambiare solo la password.&lt;/p&gt;
&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;
&lt;p&gt;Ora che abbiamo qualche dato di esempio nel database si possono cominciare a sviluppare le funzioni di visualizzazione nel frontend. Alla prossima.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5664646546631819586?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/8wRmuYSeLS3_LMKToyl0UhOkEwk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/8wRmuYSeLS3_LMKToyl0UhOkEwk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/8wRmuYSeLS3_LMKToyl0UhOkEwk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/8wRmuYSeLS3_LMKToyl0UhOkEwk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/V-rPBEAjvn0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5664646546631819586/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-dati-di-prova.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5664646546631819586?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5664646546631819586?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-creazione-dati-di-prova.html" title="symfony, creazione dati di prova (fixtures)" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;AkQCQ385fip7ImA9WxNXFUU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5929602375515471276</id><published>2009-10-02T14:25:00.003+02:00</published><updated>2009-10-03T18:46:02.126+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-03T18:46:02.126+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, generazione del Modello</title><content type="html">&lt;p&gt;Il file &lt;em&gt;schema.yml&lt;/em&gt; visto la volta scorsa (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-schema-database.html"&gt;symfony, creazione schema database&lt;/a&gt;) contiene tutte le informazioni necessarie a generare sia le classi del Modello che gli script SQL per la creazione delle tabelle del database. Prima di procedere occorre però creare un database vuoto per il nostro progetto.&lt;/p&gt;

&lt;h2&gt;Creazione database del progetto&lt;/h2&gt;

&lt;p&gt;Il database necessario alla nostra applicazione va creato con gli strumenti amministrativi di &lt;strong&gt;MySQL&lt;/strong&gt;. Può essere usato &lt;strong&gt;phpMyAdmin&lt;/strong&gt; se lo abbiamo installato sul server di sviluppo, altrimenti da una finestra terminale digitiamo&lt;/p&gt;

&lt;pre&gt;mysql --user=root --password="password root"&lt;/pre&gt;

&lt;p&gt;Poi al prompt &lt;strong&gt;mysql&amp;gt;&lt;/strong&gt; inseriamo i seguenti comandi&lt;/p&gt;

&lt;pre&gt;create database lyra character set utf8;
create user lyra@localhost identified by "user password";
grant all on lyra.* to lyra@localhost;
exit&lt;/pre&gt;

&lt;p&gt;Al posto di &lt;em&gt;user password&lt;/em&gt; va ovviamente la password desiderata per l'utente. Sul server di sviluppo si potrebbe utilizzare l'utente &lt;strong&gt;root&lt;/strong&gt; per connettersi al database, ma per abitudine preferisco creare un utente specifico.&lt;/p&gt;

&lt;p&gt;La configurazione database predefinita in simfony utilizza &lt;strong&gt;Propel&lt;/strong&gt;, è bene rimuoverla poiché per Lyra utilizziamo &lt;strong&gt;Doctrine&lt;/strong&gt;. Da una finestra terminale, posizionati nella cartella radice del progetto digitiamo&lt;/p&gt;

&lt;pre&gt;rm config/databases.yml&lt;/pre&gt;

&lt;p&gt;A questo punto creiamo la nuova configurazione con&lt;/p&gt;

&lt;pre&gt;./symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=lyra" lyra "user password"&lt;/pre&gt;

&lt;p&gt;"user password" è ovviamente la password scelta precedentemente per l'utente con i privilegi sul database &lt;em&gt;lyra&lt;/em&gt;. Il comando genera un nuovo file &lt;em&gt;config/databases.yml&lt;/em&gt; contenente i parametri di connessione.&lt;/p&gt;

&lt;h2&gt;Generazione classi del Modello&lt;/h2&gt;

&lt;p&gt;Sempre da terminale e sempre dalla cartella del progetto eseguiamo il comando&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-model&lt;/pre&gt;

&lt;p&gt;In &lt;em&gt;lib/model/doctrine&lt;/em&gt; vengono generate &lt;strong&gt;tre classi&lt;/strong&gt; per ogni tabella definita nel file &lt;em&gt;schema.yml&lt;/em&gt;. Ad esempio per la tabella &lt;strong&gt;Articoli&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;~
  sfprojects
    lyra
      lib
        model
          base
            BaseLyraArticle.class.php
            ...
          LyraArticle.class.php
          LyraArticleTable.class.php
          ...
        vendor
    ...&lt;/pre&gt;

&lt;p&gt;Le classi in &lt;em&gt;lib/model/doctrine/base&lt;/em&gt; vengono sempre rigenerate ogni volta che si esegue &lt;strong&gt;doctrine:build-model&lt;/strong&gt; quindi non devono mai essere modificate.&lt;/p&gt;

&lt;p&gt;Come vedremo le classi in &lt;em&gt;lib/model/doctrine&lt;/em&gt; possono invece essere personalizzate. In effetti sono create vuote da Doctrine. Ad esempio:&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

&amp;lt;?php
/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
class LyraArticle extends BaseLyraArticle
{

}&lt;/pre&gt;

&lt;h2&gt;Generazione script SQL&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:build-sql&lt;/pre&gt;

&lt;p&gt;Dopo l'esecuzione del comando, in &lt;em&gt;data/sql/schema.sql&lt;/em&gt; troviamo lo script per la creazione delle tabelle del database. Si può notare come siano state create le opportune chiavi esterne per ogni relazione che abbiamo definito in &lt;em&gt;schema.yml&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Per eseguire lo script SQL e creare le tabelle nel database si utilizza il comando&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:insert-sql&lt;/pre&gt;

&lt;h2&gt;Generazione moduli &lt;/h2&gt;

&lt;p&gt;Sempre a partire dalle informazioni contenute nel file &lt;em&gt;schema.yml&lt;/em&gt;, &lt;strong&gt;Doctrine&lt;/strong&gt; è in grado di generare le classi per la gestione dei moduli di inserimento dati e la loro validazione.&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-forms&lt;/pre&gt;

&lt;p&gt;Dopo l'esecuzione del comando troveremo classi base (in &lt;em&gt;lib/form/doctrine/base&lt;/em&gt;) e classi derivate (in &lt;em&gt;lib/form/doctrine&lt;/em&gt;) che definiscono i moduli di inserimento e modifica dei dati con le relative regole di validazione per ogni tabella dello schema. Anche in questo caso bisogna ricordare che le classi nella sottocartella &lt;em&gt;base&lt;/em&gt; non devono essere modificate in quanto sovrascritte ogni volta che eseguiremo il comando. Le classi in &lt;em&gt;lib/form/doctrine&lt;/em&gt; possono invece contenere nostro codice come vedremo in seguito.&lt;/p&gt;

&lt;p&gt;Infine esiste un comando scorciatoia&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-all&lt;/pre&gt;

&lt;p&gt;con il quale si eseguono in sequenza: &lt;/p&gt;

&lt;ul&gt;&lt;li&gt;doctrine:build-model&lt;/li&gt;
&lt;li&gt;doctrine:build-sql&lt;/li&gt;
&lt;li&gt;doctrine:build-forms&lt;/li&gt;
&lt;li&gt;doctrine:insert-sql&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I risultati di tutto questo lavoro, più del framework che nostro, sono fissati alla &lt;strong&gt;revisione 6&lt;/strong&gt; del progetto su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Adesso che abbiamo il database è il caso di metterci dentro un po' di dati almeno per cominciare a vedere qualcosa nel frontend della nostra applicazione. Questo è quello che faremo la prossima volta.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5929602375515471276?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/49-luxBj1KfdmqALznMAW6K9R60/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/49-luxBj1KfdmqALznMAW6K9R60/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/49-luxBj1KfdmqALznMAW6K9R60/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/49-luxBj1KfdmqALznMAW6K9R60/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/Uw7bc7ZSkkc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5929602375515471276/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-generazione-del-modello.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5929602375515471276?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5929602375515471276?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/10/symfony-generazione-del-modello.html" title="symfony, generazione del Modello" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C04NRHY6fyp7ImA9WxNXE0w.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8516110287253564744</id><published>2009-09-30T13:55:00.001+02:00</published><updated>2009-09-30T13:59:55.817+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-30T13:59:55.817+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, creazione schema database (schema.yml)</title><content type="html">&lt;p&gt;A partire dalle specifiche iniziali dell'applicazione viste in precedenza, iniziamo a lavorare sul modello dati di &lt;strong&gt;Lyra&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Attivazione Doctrine&lt;/h2&gt;

&lt;p&gt;L'applicazione utilizzerà &lt;strong&gt;Doctrine&lt;/strong&gt; come ORM quindi è necessario attivare il relativo plugin in &lt;em&gt;config/ProjectConfiguration.class.php&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;public function setup()
{
  $this-&gt;enablePlugins(array('sfDoctrinePlugin'));
  $this-&gt;disablePlugins(array('sfPropelPlugin'));
}&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Occorre poi pulire la cache di symfony con&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;./symfony cc&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Poiché abbiamo abilitato un nuovo plugin bisogna utilizzare il comando &lt;strong&gt;plugin:publish-assets&lt;/strong&gt; per consentire l'installazione di eventuali file accessori (immagini, css, javascript) necessari al funzionamento del plugin.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;./symfony plugin:publish-assets&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Infine è bene rimuovere ogni riferimento a &lt;strong&gt;Propel&lt;/strong&gt; (l'ORM alternativo a &lt;strong&gt;Doctrine&lt;/strong&gt;) che non sarà utilizzato.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;rm web/sfPropelPlugin
rm config/propel.ini
rm config/schema.yml&lt;/pre&gt;&lt;/p&gt;

&lt;h2&gt;Creazione dello schema del database&lt;/h2&gt;

&lt;p&gt;In symfony la struttura delle tabelle necessarie all'applicazione viene definita in un file in formato &lt;strong&gt;YAML&lt;/strong&gt; (&lt;em&gt;schema.yml&lt;/em&gt;). Per maggiori dettagli su questo formato si può consultare il sito ufficiale (http://yaml.org/).&lt;/p&gt;

&lt;p&gt;Si tratta comunque di un formato abbastanza intuitivo e non difficile da utilizzare una volta visti alcuni esempi.&lt;/p&gt;

&lt;p&gt;Il file &lt;em&gt;schema.yml&lt;/em&gt; deve essere collocato in una cartella &lt;em&gt;config/doctrine&lt;/em&gt; che va creata&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;mkdir config/doctrine&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Le tabelle necessarie per questa versione preliminare di Lyra sono:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;Tipi di contenuto&lt;/li&gt;
&lt;li&gt;Articoli&lt;/li&gt;
&lt;li&gt;Commenti&lt;/li&gt;
&lt;li&gt;Cataloghi&lt;/li&gt; 
&lt;li&gt;Etichette&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Non è possibile fare adesso un esame dettagliato del file &lt;em&gt;schema.yml&lt;/em&gt;. La versione completa può essere scaricata da &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/config/doctrine/schema.yml#"&gt;Google Code&lt;/a&gt;. Inoltre tutti i dettagli sulla struttura dei file sono disponibili sulla documentazione ufficiale di symfony.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;
config/doctrine/schema.yml
  
  LyraArticle:
    tableName: articles&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LyraArticle&lt;/strong&gt; è il nome della classe autogenerata da &lt;strong&gt;Doctrine&lt;/strong&gt; che, come si vedrà, utilizzeremo per gestire i record della tabella articoli, &lt;em&gt;articles&lt;/em&gt; è il nome della tabella sul database.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;actAs:
    Timestampable: ~
    Sluggable:
      fields: [title]
      canUpdate: true
      unique: true
      indexName: article_slug&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;actAs&lt;/strong&gt; indica una serie di comportamenti predefiniti del modello implementati da &lt;strong&gt;Doctrine&lt;/strong&gt; senza che si debba scrivere codice.&lt;/p&gt;

&lt;h3&gt;Timestampable&lt;/h3&gt;

&lt;p&gt;Alla tabella verranno aggiunti due campi &lt;strong&gt;created_at&lt;/strong&gt;, &lt;strong&gt;updated_at&lt;/strong&gt; che saranno mantenuti automaticamente aggiornati con la data di creazione e di ultima modifica del record.&lt;/p&gt;

&lt;h3&gt;Sluggable&lt;/h3&gt;

&lt;p&gt;Alla tabella verrà aggiunto un campo &lt;strong&gt;slug&lt;/strong&gt; con lo stesso contenuto del campo &lt;strong&gt;title&lt;/strong&gt; modificato in questo modo:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;gli spazi sono trasformati in '-';&lt;/li&gt;
&lt;li&gt;i caratteri diversi da lettere e numeri sono rimossi;&lt;/li&gt;
&lt;li&gt;i caratteri maiuscoli sono trasformati in minuscoli;&lt;/li&gt;
&lt;li&gt;i caratteri accentati sono convertiti nell'equivalente non accentato.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Se ad esempio il campo &lt;strong&gt;title&lt;/strong&gt; di un record contiene&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;Però che bell'articolo&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;il campo &lt;strong&gt;slug&lt;/strong&gt; conterrà&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;pero-che-bell-articolo&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;In questo modo il campo slug può essere utilizzato per generare le &lt;strong&gt;URL SEF&lt;/strong&gt;. &lt;strong&gt;canUpdate&lt;/strong&gt; impostato a &lt;em&gt;true&lt;/em&gt; consente la modifica da parte dell'utente del campo generato automaticamente, &lt;strong&gt;unique&lt;/strong&gt; impostato a &lt;em&gt;true&lt;/em&gt; indica che il campo deve essere univoco nella tabella: se due articoli hanno lo stesso identico titolo, sarà utilizzato un suffisso numerico progressivo nello slug per differenziarli. Con &lt;strong&gt;indexName&lt;/strong&gt; indichiamo il nome da dare all'indice sul campo slug.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;options:
    collate: utf8_unicode_ci
    charset: utf8&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Impostiamo set di caratteri e collazione in utf-8.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    title:
      type: string(255)
      notnull: true
...&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;columns&lt;/em&gt; definiamo i campi della tabella. Con &lt;strong&gt;primary&lt;/strong&gt; e &lt;strong&gt;autoincrement&lt;/strong&gt; a &lt;em&gt;true&lt;/em&gt; indichiamo che il campo &lt;strong&gt;id&lt;/strong&gt; sarà la chiave primaria autoincrementante della tabella, con &lt;strong&gt;type&lt;/strong&gt; impostiamo il tipo del campo. Nello schema si utilizzano tipi indipendenti dal database utilizzato. Al momento della generazione degli script &lt;strong&gt;SQL&lt;/strong&gt; per la creazione delle tabelle, sarà &lt;strong&gt;Doctrine&lt;/strong&gt; a convertire questi tipi in quelli corrispondenti al particolare database scelto. &lt;/p&gt;

&lt;p&gt;Ecco le corrispondenze tra i tipi di dato &lt;strong&gt;Doctrine&lt;/strong&gt; utilizzati nello schema che stiamo esaminando e tipi &lt;strong&gt;MySQL&lt;/strong&gt;:&lt;/p&gt;

&lt;table&gt;&lt;tr&gt;&lt;th&gt;Doctrine&lt;/th&gt;&lt;th&gt;MySql&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;integer(1)&lt;/td&gt;&lt;td&gt;tinyint&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;integer(4)&lt;/td&gt;&lt;td&gt;int&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;boolean&lt;/td&gt;&lt;td&gt;tinyint(1)&lt;/td&gt;&lt;/tr&gt;    
&lt;tr&gt;&lt;td&gt;clob&lt;/td&gt;&lt;td&gt;longtext&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clob(65532)&lt;/td&gt;&lt;td&gt;text&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;timestamp&lt;/td&gt;&lt;td&gt;datetime&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Per quanto riguarda i tipi di dato &lt;em&gt;string&lt;/em&gt;, se la lunghezza è inferiore o uguale a 255 corripondono al tipo &lt;strong&gt;MySQL&lt;/strong&gt; &lt;em&gt;varchar&lt;/em&gt; della stessa lunghezza, se la lunghezza è superiore a 255 corrispondono a &lt;em&gt;text&lt;/em&gt;, &lt;em&gt;mediumtext&lt;/em&gt; o &lt;em&gt;longtext&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ometto i campi rimanenti della tabella &lt;strong&gt;Articoli&lt;/strong&gt; e passo alla tabella &lt;strong&gt;Commenti&lt;/strong&gt; soffermandomi però solo sulla parte più significativa.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;LyraComment:
...

columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    article_id:
      type: integer(4)
...
relations:
    CommentArticle:
      class: LyraArticle
      local: article_id
      foreign: id
      foreignAlias: ArticleComments
      onDelete: CASCADE&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Nello schema dobbiamo anche definire le relazioni tra tabelle, in questo caso la relazione &lt;strong&gt;uno a molti&lt;/strong&gt; tra la tabella &lt;strong&gt;Articoli&lt;/strong&gt; e la tabella &lt;strong&gt;Commenti&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;class&lt;/strong&gt;: nome della classe della tabella correlata (&lt;strong&gt;Articoli&lt;/strong&gt;);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;local&lt;/strong&gt;: nome del campo di relazione nella tabella locale (&lt;strong&gt;Commenti&lt;/strong&gt;);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;foreign&lt;/strong&gt;: nome del campo di relazione nella tabella correlata;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;foreignAlias&lt;/strong&gt;: nome della relazione vista 'dall'altro lato', cioè da quello della tabella &lt;strong&gt;Articoli&lt;/strong&gt;. Vedremo in seguito a cosa serve;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;onDelete&lt;/strong&gt;: impostato su &lt;em&gt;CASCADE&lt;/em&gt; determina la cancellazione automatica dei commenti quando il record correlato in &lt;strong&gt;Articoli&lt;/strong&gt; viene cancellato.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Nella parte restante del file &lt;em&gt;schema.yml&lt;/em&gt; si trova anche un esempio di come definire una relazione &lt;strong&gt;molti a molti&lt;/strong&gt;, quella tra gli &lt;strong&gt;Articoli&lt;/strong&gt; e le &lt;strong&gt;Etichette&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vedremo la prossima volta come a partire dallo schema saranno generate le classi del Modello e gli script SQL per la creazione delle tabelle.&lt;/p&gt;

&lt;p&gt;Ho eseguito il commit della revisione 5 su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8516110287253564744?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BdHK5U8VzQ5BWvlzYUCbk5sAE3o/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BdHK5U8VzQ5BWvlzYUCbk5sAE3o/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/BdHK5U8VzQ5BWvlzYUCbk5sAE3o/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BdHK5U8VzQ5BWvlzYUCbk5sAE3o/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/IHxYy6UKc4Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8516110287253564744/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-schema-database.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8516110287253564744?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8516110287253564744?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-schema-database.html" title="symfony, creazione schema database (schema.yml)" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06652152356799506856" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry></feed>
