<?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;A0cGSHw_eCp7ImA9WxNUGUo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007</id><updated>2009-11-11T23:37:09.240+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>89</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/sviluppare-in-rete" type="application/atom+xml" /><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;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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/kXeqOEg2XnD9QLbAdJnJKeRDZ8w/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kXeqOEg2XnD9QLbAdJnJKeRDZ8w/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/kXeqOEg2XnD9QLbAdJnJKeRDZ8w/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kXeqOEg2XnD9QLbAdJnJKeRDZ8w/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/zRd11t0P_PzHVuS464tqoCGONyU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/zRd11t0P_PzHVuS464tqoCGONyU/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/zRd11t0P_PzHVuS464tqoCGONyU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/zRd11t0P_PzHVuS464tqoCGONyU/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/jAmWJ2xPDY4Of01TXli5Snq82Qo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/jAmWJ2xPDY4Of01TXli5Snq82Qo/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/jAmWJ2xPDY4Of01TXli5Snq82Qo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/jAmWJ2xPDY4Of01TXli5Snq82Qo/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/DJ0M01iUxCIkJlujA0vC6Hn2mic/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/DJ0M01iUxCIkJlujA0vC6Hn2mic/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/DJ0M01iUxCIkJlujA0vC6Hn2mic/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/DJ0M01iUxCIkJlujA0vC6Hn2mic/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/QRG2OOlf198pjIoQvorkOpbwWeE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/QRG2OOlf198pjIoQvorkOpbwWeE/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/QRG2OOlf198pjIoQvorkOpbwWeE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/QRG2OOlf198pjIoQvorkOpbwWeE/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/uG6hmRsaTSR_UX09Dp8SfSht570/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/uG6hmRsaTSR_UX09Dp8SfSht570/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/uG6hmRsaTSR_UX09Dp8SfSht570/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/uG6hmRsaTSR_UX09Dp8SfSht570/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/YRHIUNDScn-HbG7LT3y4tkZODwE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/YRHIUNDScn-HbG7LT3y4tkZODwE/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/YRHIUNDScn-HbG7LT3y4tkZODwE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/YRHIUNDScn-HbG7LT3y4tkZODwE/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_bxm-IkRJXieaU-iFmMq63l8KFs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_bxm-IkRJXieaU-iFmMq63l8KFs/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/_bxm-IkRJXieaU-iFmMq63l8KFs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_bxm-IkRJXieaU-iFmMq63l8KFs/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/M_lGm5RL0G8kkTwGauO1lxX_Z24/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/M_lGm5RL0G8kkTwGauO1lxX_Z24/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/M_lGm5RL0G8kkTwGauO1lxX_Z24/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/M_lGm5RL0G8kkTwGauO1lxX_Z24/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/M1xfyzsUB27c-iNomcSXb-PwCTk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/M1xfyzsUB27c-iNomcSXb-PwCTk/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/M1xfyzsUB27c-iNomcSXb-PwCTk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/M1xfyzsUB27c-iNomcSXb-PwCTk/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/td_JDdsRQ3NOr1OARDFTBPk-V00/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/td_JDdsRQ3NOr1OARDFTBPk-V00/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/td_JDdsRQ3NOr1OARDFTBPk-V00/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/td_JDdsRQ3NOr1OARDFTBPk-V00/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BdCE80h_sxXvZvF-3c_eumg3MSY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BdCE80h_sxXvZvF-3c_eumg3MSY/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/BdCE80h_sxXvZvF-3c_eumg3MSY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BdCE80h_sxXvZvF-3c_eumg3MSY/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/EgGPWw73jyrxu6De-rNIOmYAqNw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EgGPWw73jyrxu6De-rNIOmYAqNw/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/EgGPWw73jyrxu6De-rNIOmYAqNw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EgGPWw73jyrxu6De-rNIOmYAqNw/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/xz8z1Cdk6AKa7hejrJwtnzp52jc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xz8z1Cdk6AKa7hejrJwtnzp52jc/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/xz8z1Cdk6AKa7hejrJwtnzp52jc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xz8z1Cdk6AKa7hejrJwtnzp52jc/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/MJmLhCBdaVpznJuM1F6DBXXOpgA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MJmLhCBdaVpznJuM1F6DBXXOpgA/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/MJmLhCBdaVpznJuM1F6DBXXOpgA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MJmLhCBdaVpznJuM1F6DBXXOpgA/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/0NF5D2oqCuZ-lLl03spMFJ646Xg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0NF5D2oqCuZ-lLl03spMFJ646Xg/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/0NF5D2oqCuZ-lLl03spMFJ646Xg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0NF5D2oqCuZ-lLl03spMFJ646Xg/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'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/AzTu4xgjDitwKVsr3tsUIPgIj4U/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/AzTu4xgjDitwKVsr3tsUIPgIj4U/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/AzTu4xgjDitwKVsr3tsUIPgIj4U/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/AzTu4xgjDitwKVsr3tsUIPgIj4U/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><entry gd:etag="W/&quot;D0QFSHkyfyp7ImA9WxNXEk8.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1391617140181825900</id><published>2009-09-29T13:53:00.000+02:00</published><updated>2009-09-29T13:55:19.797+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-29T13:55:19.797+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, creazione progetto e applicazione</title><content type="html">&lt;p&gt;In simfony un &lt;strong&gt;progetto&lt;/strong&gt; è suddiviso in una o più &lt;strong&gt;applicazioni&lt;/strong&gt;. È abbastanza comune avere almeno un'applicazione per il &lt;strong&gt;frontend&lt;/strong&gt; e un'applicazione per il &lt;strong&gt;backend&lt;/strong&gt; ed è così che sarà strutturato &lt;strong&gt;Lyra&lt;/strong&gt;. Procediamo con ordine e creiamo per prima cosa il progetto. Da terminale e posizionati nella cartella radice del progetto (che, ripeto, nel mio caso è &lt;em&gt;~/sfprojects/lyra&lt;/em&gt;) digitiamo il comando&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;php lib/vendor/symfony/data/bin/symfony generate:project lyra&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Il risultato sarà la creazione di un bel numero di cartelle che si aggiungono alla cartella &lt;em&gt;lib&lt;/em&gt; che abbiamo creato la volta scorsa e che contiene il framework. L'albero delle cartelle del progetto è ora il seguente (riporto solo il primo livello, alcune cartelle contengono ulteriori sotto cartelle)&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;~
  sfprojects
    lyra
      apps
      cache
      config
      data
      doc
      lib
      log
      plugins
      test
      web&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Tra i vari file creati dal comando generate:project, nella cartella radice del progetto ce n'è uno chiamato &lt;em&gt;symfony&lt;/em&gt; che ci permetterà da ora in poi di eseguire i comandi senza specificare il percorso completo (&lt;em&gt;lib/vendor/symfony/data/bin/symfony&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;In Linux ad esempio basterà digitare dalla cartella radice del progetto &lt;/p&gt;

&lt;p&gt;&lt;pre&gt;./symfony &lt;em&gt;comando&lt;/em&gt; &lt;em&gt;opzioni&lt;/em&gt; &lt;em&gt;argomenti&lt;/em&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Con il comando &lt;strong&gt;generate:app&lt;/strong&gt; creiamo l'applicazione di &lt;strong&gt;frontend&lt;/strong&gt; per il nostro progetto&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;./symfony generate:app --escaping-strategy=on --csrf-secret=gfhtyy87 frontend&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Dei due parametri &lt;strong&gt;escaping-strategy&lt;/strong&gt; e &lt;strong&gt;csrf-secret&lt;/strong&gt; (a quest'ultimo si passa una sequenza di caratteri scelta a caso) ci sarà modo di parlare in seguito, per ora dico solo che è opportuno specificarli perché impostano strategie predefinite importanti per la sicurezza dell'applicazione.&lt;/p&gt;

&lt;p&gt;Per effetto di questo comando una serie di cartelle sono create all'interno della cartella &lt;em&gt;apps&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;~
  sfprojects
    lyra
      apps
        frontend
          config
          i18n
          lib
          modules
          templates
...&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Prima di proseguire è bene fare subito una modifica al file di configurazione del progetto per renderlo facilmente rilocabile da un server ad un altro. All'inizio di &lt;em&gt;config/ProjectConfiguration.class.php&lt;/em&gt; troviamo questa riga &lt;/p&gt;

&lt;p&gt;&lt;pre&gt;require_once '/home/gmassi/sfprojects/lyra/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Ovviamente chi ha eseguito le varie operazioni sul proprio server di sviluppo avrà il percorso alla propria cartella. Modificare la riga così:&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Arrivati a questo punto siamo quasi in grado di visualizzare qualcosa nel browser. Prima però è necessario configurare un &lt;strong&gt;host virtuale&lt;/strong&gt; per il progetto. Ecco la procedura da seguire (Linux Ubuntu).&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;sudo cp /etc/apache2/sites-available/default /etc/apache2/sites-available/lyra
sudo gedit /etc/apache2/sites-available/lyra&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Modificare il file di configurazione in questo modo (ovviamente con i &lt;strong&gt;propri&lt;/strong&gt; percorsi al posto di '&lt;em&gt;/home/gmassi/...&lt;/em&gt;')&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;
&amp;lt;VirtualHost *:80&amp;gt;
  ServerAdmin webmaster@localhost
  ServerName lyra
  DocumentRoot /home/gmassi/sfprojects/lyra/web
  &amp;lt;Directory /&amp;gt;
    Options FollowSymLinks
    AllowOverride None
  &amp;lt;/Directory&amp;gt;
  &amp;lt;Directory /home/gmassi/sfprojects/lyra/web&amp;gt;
    AllowOverride All
    Allow from All
  &amp;lt;/Directory&amp;gt;

  Alias /sf /home/gmassi/sfprojects/lyra/lib/vendor/symfony/data/web/sf
  &amp;lt;Directory &amp;quot;/home/gmassi/sfprojects/lyra/lib/vendor/symfony/data/web/sf&amp;quot;&amp;gt;
    AllowOverride All
    Allow from All
  &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Abilitare l'host virtuale con&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;sudo a2ensite lyra
sudo /etc/init.d/apache2 restart&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Per finire,&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;sudo gedit /etc/hosts&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;e aggiungere l'host virtuale appena creato sulla prima riga dopo localhost&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;127.0.0.1 localhost lyra&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Possiamo lanciare il browser e scrivere nella barra degli indirizzi&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;http://lyra/index.php/&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Se abbiamo &lt;strong&gt;mod_rewrite&lt;/strong&gt; abilitato sul server di sviluppo (consigliabile) va bene anche semplicemente&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;http://lyra/&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Se tutti i passaggi sono stati seguiti correttamente comparirà la pagina di benvenuto predefinita di simfony.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SsFd0A_QeTI/AAAAAAAAAGQ/paLi8zDY3Nk/s1600-h/symfony.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 85px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SsFd0A_QeTI/AAAAAAAAAGQ/paLi8zDY3Nk/s320/symfony.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5386689777511725362" /&gt;&lt;/a&gt;
&lt;p&gt;Ho eseguito il commit della revisione 4 su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;. A questo punto per crearsi localmente il proprio ambiente di sviluppo si hanno due possibilità:&lt;/p&gt;

&lt;p&gt;1) Si eseguono in sequenza le operazioni descritte nell'articolo precedente (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/installazione-del-framework-symfony.html"&gt;Installazione del framework&lt;/a&gt;) ed in questo.&lt;/p&gt;

&lt;p&gt;2) Ci si limita a creare una propria cartella locale per il progetto ad esempio &lt;em&gt;~/sfprojects/lyra&lt;/em&gt; e da una finestra terminale si esegue il checkout della revisione 4. Chiaramente il client Subversion deve essere installato.&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;cd ~/sfprojects/lyra
svn checkout -r4 http://lyra-cms.googlecode.com/svn/trunk/ .&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Poi si assegnano i permessi alle cartelle &lt;em&gt;cache&lt;/em&gt; e &lt;em&gt;log&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;chmod 777 cache
chmod 777 log&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Infine si seguono le indicazioni presenti in questo articolo per creare un host virtuale che abbia come &lt;strong&gt;Document Root&lt;/strong&gt; la cartella &lt;em&gt;web&lt;/em&gt; all'interno della propria cartella del progetto.&lt;/p&gt;

&lt;p&gt;Dalla prossima volta si inizierà lo sviluppo del modello dati specifico per la nostra applicazione.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1391617140181825900?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/e65Z3wC8BSo5-wmnwJny80hmG7g/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/e65Z3wC8BSo5-wmnwJny80hmG7g/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/e65Z3wC8BSo5-wmnwJny80hmG7g/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/e65Z3wC8BSo5-wmnwJny80hmG7g/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/IgRb8fap1SI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1391617140181825900/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1391617140181825900?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1391617140181825900?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html" title="symfony, creazione progetto e applicazione" /><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/SsFd0A_QeTI/AAAAAAAAAGQ/paLi8zDY3Nk/s72-c/symfony.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;D0YMQH0_eip7ImA9WxNXEk8.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3460888474717808701</id><published>2009-09-28T09:41:00.002+02:00</published><updated>2009-09-29T13:53:01.342+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-29T13:53:01.342+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Installazione del framework symfony</title><content type="html">&lt;p&gt;È il momento di iniziare lo sviluppo di &lt;strong&gt;Lyra&lt;/strong&gt;. Come ho già detto detto utilizzerò Linux Ubuntu come server di sviluppo. Comunque la documentazione ufficiale (a partire dal già citato &lt;a title="Guida symfony" href="http://www.symfony-project.org/getting-started/1_2/it/"&gt;Getting started with symfony&lt;/a&gt;) riporta le istruzioni necessarie a configurare ed utilizzare symfony anche sotto Windows.&lt;/p&gt;

&lt;p&gt;La prima cosa da fare è creare una cartella per il progetto, io ho scelto di crearla all'interno di una cartella &lt;em&gt;sfprojects&lt;/em&gt; nella mia 'home'. Essendo &lt;em&gt;gmassi&lt;/em&gt; il mio mome utente su Ubuntu il percorso completo è&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/home/gmassi/sfprojects/lyra&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;All'interno di questa cartella ne va creata un'altra &lt;em&gt;lib&lt;/em&gt; e al suo interno un'altra ancora &lt;em&gt;vendor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;/
  home
    gmassi
      sfprojects
        lyra
          lib
            vendor&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;A questo punto possiamo installare il framework. Dalla &lt;a href="http://www.symfony-project.org/installation/1_2" title="Scaricare symfony 1.2"&gt;pagina di download&lt;/a&gt; scarichiamo il pacchetto con l'ultima versione disponibile di symfony (symfony-1.2.8.tgz) e lo decomprimiamo nella cartella &lt;em&gt;vendor&lt;/em&gt;. La decompressione dell'archivio genera una cartella &lt;em&gt;symfony-1.2.8&lt;/em&gt; che deve essere rinominata in &lt;em&gt;symfony&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Il risultato finale dell'operazione deve essere questo:&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;
/
  home
    gmassi
      sfprojects
        lyra
          lib
            vendor
              symfony
                data
                docs
                lib
                licenses
                test
&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Mostro solo il primo livello sotto la cartella &lt;em&gt;symfony&lt;/em&gt;, ma ci sono ovviamente altre sottocartelle. Per verificare che l'installazione del framework sia andata bene posizioniamoci nella cartella del progetto digitando in una finestra terminale&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd ~/sfprojects/lyra&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A beneficio dei meno esperti di Linux ricordo che ~ (tilde) indica la cartella 'home' dell'utente corrente (nel mio caso &lt;em&gt;/home/gmassi&lt;/em&gt;). Quindi verifichiamo il numero di versione di symfony con il comando&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php lib/vendor/symfony/data/bin/symfony -V&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Come ho già accennato precedentemente, già da qui si può notare che symfony richiede di poter eseguire script PHP da linea di comando almeno sul server di sviluppo.&lt;/p&gt;

&lt;p&gt;A questo punto eseguo il commit delle modifiche su &lt;a title="Lyra repository" href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;. Siamo alla &lt;strong&gt;revisione 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alla fine di ogni articolo scriverò sempre il numero di revisione corrente. Ogni client &lt;strong&gt;Subversion&lt;/strong&gt; consente di estrarre dal repository con un'operazione di &lt;strong&gt;checkout&lt;/strong&gt; o &lt;strong&gt;update&lt;/strong&gt; lo stato dell'applicazione aggiornato ad un determinato numero di revisione. In questo modo non solo si avrà sempre a disposizione la versione più recente dell'applicazione, ma si potranno ricostruire tutte le fasi dello sviluppo attraverso le diverse revisioni.&lt;/p&gt;

&lt;p&gt;Ad esempio se, come ho fatto io, si è creata la cartella del progetto come &lt;em&gt;sfprojects/lyra&lt;/em&gt; nella propria 'home', e si è installato il client Subversion, per scaricare da Google Code l'applicazione al punto esatto dello sviluppo a cui siamo arrivati con questo articolo si possono utilizzare queste istruzioni da terminale&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;cd ~/sfprojects/lyra
svn checkout -r3 http://lyra-cms.googlecode.com/svn/trunk/ .&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Attenzione al punto al termine della seconda riga, è importante metterlo. Se si omette l'opzione &lt;strong&gt;-r&lt;/strong&gt; si scaricherà la versione più recente dell'applicazione.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3460888474717808701?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/APd0N2vCvPamfGRcOupMSKI2WNE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/APd0N2vCvPamfGRcOupMSKI2WNE/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/APd0N2vCvPamfGRcOupMSKI2WNE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/APd0N2vCvPamfGRcOupMSKI2WNE/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/ggnHIrX2mkA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3460888474717808701/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/installazione-del-framework-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3460888474717808701?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3460888474717808701?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/installazione-del-framework-symfony.html" title="Installazione del framework 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;D0MASXk9fip7ImA9WxNQGEs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-2376872935974369245</id><published>2009-09-25T09:53:00.002+02:00</published><updated>2009-09-25T09:57:28.766+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-25T09:57:28.766+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Realizzare un cms con symfony</title><content type="html">&lt;p&gt;Come promesso la &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/introduzione-symfony.html"&gt;volta scorsa&lt;/a&gt; vediamo qualche dettaglio sull'applicazione che svilupperemo con symfony. Trattandosi di un framework è evidente che symfony consente la realizzazione di qualsiasi tipo di applicazione, io per questa serie di articoli ho scelto di sviluppare un semplice (ma espandibile) &lt;strong&gt;gestore di contenuti&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Con tutte le piattaforme blog e i cms disponibili si potrebbe chiedere che senso abbia svilupparne uno con un framework. Mi vengono in mente alcune possibili riposte, elencate in ordine di importanza:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;Perché può essere divertente farlo;&lt;/li&gt;
&lt;li&gt;Perché mentre lo si fa si rischia di imparare qualcosa;&lt;/li&gt;
&lt;li&gt;Perché si ha l'opportunità di costruirsi un prodotto su misura per le proprie esigenze.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Le funzionalità iniziali del cms saranno le seguenti, di sicuro mi dimentico qualcosa:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;Gestione di più tipologie di contenuto;&lt;/li&gt;
&lt;li&gt;inserimento contenuti da backend e frontend anche con editor WYSIWYG;&lt;/li&gt;
&lt;li&gt;catalogazione dei contenuti in categorie, sottocategorie nidificate in più livelli;&lt;/li&gt;
&lt;li&gt;"alberi" di categorie diversi a seconda del tipo di contenuto;&lt;/li&gt;
&lt;li&gt;gestione dei commenti;&lt;/li&gt;
&lt;li&gt;gestione feed;&lt;/li&gt;
&lt;li&gt;URL semplificate o URL SEF come si preferisce chiamarle.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Ecco almeno a grandi linee come si procederà. Dopo la creazione della struttura del database e l'inserimento di alcuni dati iniziali si inizierà lo sviluppo del frontend: visualizzazione articoli in prima pagina (con sommario e link 'leggi tutto'), visualizzazione articolo a pagina intera, creazione e modifica articoli, inserimento commenti, visualizzazione articoli per categoria.&lt;/p&gt;

&lt;p&gt;Si passerà poi al backend: gestione categorie, che si chiameranno &lt;strong&gt;etichette&lt;/strong&gt; e saranno suddivise in &lt;strong&gt;cataloghi&lt;/strong&gt;, gestione articoli e commenti (approvazione, modifica, cancellazione), a seguire una iniziale gestione utenti.&lt;/p&gt;

&lt;p&gt;Il primo traguardo è arrivare ad un'applicazione in grado di gestire un blog personale o comunque un sito in cui la gestione dei contenuti sia centralizzata nelle mani dell'amministratore. Inizialmente sarà disponibile un solo tipo di contenuto (&lt;strong&gt;articolo&lt;/strong&gt;) e il tutto sarà abbastanza spartano. Da lì si partirà per perfezionare le funzioni esistenti, migliorare la gestione utenti e sviluppare la gestione di ulteriori tipi di contenuto.&lt;/p&gt;

&lt;p&gt;Serve a questo punto dare un nome all'applicazione, mi è piaciuto &lt;strong&gt;Lyra&lt;/strong&gt;, scegliere una licenza, la mia preferita è la &lt;strong&gt;GNU/GPL&lt;/strong&gt; (versione 2) e infine predisporre un repository per il codice perché sarebbe impossibile allegare i listati nel blog. A questo scopo ho scelto &lt;strong&gt;Google Code&lt;/strong&gt;. L'uso di un repository &lt;strong&gt;Subversion&lt;/strong&gt; permette tra l'altro di avere sempre a disposizione i vari stadi dello sviluppo dell'applicazione (attraverso le diverse revisioni) e quindi di seguire meglio il filo del discorso.&lt;/p&gt;

&lt;p&gt;Dalla prossima volta inizieremo in concreto lo sviluppo.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-2376872935974369245?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/vAGPo-GzYhhqPFx76PtucBpHzsM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vAGPo-GzYhhqPFx76PtucBpHzsM/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/vAGPo-GzYhhqPFx76PtucBpHzsM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vAGPo-GzYhhqPFx76PtucBpHzsM/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/rMq-jdfEBLU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/2376872935974369245/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/realizzare-un-cms-con-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2376872935974369245?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2376872935974369245?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/realizzare-un-cms-con-symfony.html" title="Realizzare un cms con 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;D0IESHoyeCp7ImA9WxNQF00.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3455985464617402342</id><published>2009-09-23T13:31:00.000+02:00</published><updated>2009-09-23T13:31:49.490+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-23T13:31:49.490+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Introduzione a symfony</title><content type="html">&lt;p&gt;Prima di iniziare lo sviluppo con &lt;strong&gt;symfony&lt;/strong&gt; dell'applicazione di esempio cui ho accennato la volta scorsa, penso siano utili alcune considerazioni preliminari.&lt;/p&gt;

&lt;p&gt;L'applicazione sarà sviluppata con &lt;strong&gt;symfony 1.2.8&lt;/strong&gt;. L'installazione include due plugin alternativi (Propel e Doctrine) con funzioni di &lt;strong&gt;ORM&lt;/strong&gt; (&lt;strong&gt;Object Relational Mapper&lt;/strong&gt;) che mettono a disposizione un'interfaccia ad oggetti per creare ed eseguire query &lt;strong&gt;SQL&lt;/strong&gt; e compiere tutte le operazioni sul database. Sarà utilizzato &lt;strong&gt;Doctrine&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Requisiti&lt;/h2&gt;

&lt;p&gt;La versione del framework che utilizzeremo richiede &lt;strong&gt;PHP&lt;/strong&gt; in versione &lt;strong&gt;5.2.4&lt;/strong&gt; o superiore. Sono poi necessari un web server ed un server di database. Io utilizzerò &lt;strong&gt;Apache 2.2&lt;/strong&gt; e &lt;strong&gt;MySql 5.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PHP dovrà essere configurato per eseguire script anche da linea di comando non solo tramite web server in quanto durante lo sviluppo symfony richiede l'esecuzione di diverse istruzioni da linea di comando.&lt;/p&gt;

&lt;h2&gt;Ambiente di sviluppo&lt;/h2&gt;

&lt;p&gt;Naturalmente è opportuno realizzare l'applicazione su un server di sviluppo locale per poi trasferirla sul server online. Io utilizzerò un normalissimo &lt;strong&gt;Linux Ubuntu 9.0.4&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A chi dovesse installare da zero l'ambiente di sviluppo locale, consiglierei:&lt;/p&gt;

&lt;h3&gt;Per Windows&lt;/h3&gt;

&lt;p&gt;Installare &lt;a href="http://www.wampserver.com/en/index.php"&gt;Wamp&lt;/a&gt; o &lt;a href="http://www.apachefriends.org/en/xampp.html"&gt;Xampp&lt;/a&gt; per Windows.&lt;/p&gt;

&lt;h3&gt;Per Linux&lt;/h3&gt;

&lt;p&gt;1) Installare le varie componenti separatamente&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://help.ubuntu-it.org/9.04/ubuntu/serverguide/it/httpd.html"&gt;Installazione Apache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://help.ubuntu-it.org/9.04/ubuntu/serverguide/it/php5.html"&gt;Installazione PHP 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://help.ubuntu-it.org/9.04/ubuntu/serverguide/it/mysql.html"&gt;Installazione MySQL&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Queste guide sono per Ubuntu, se necessario guide specifiche per diverse distribuzioni di Linux si trovano facilmente su Google.&lt;/p&gt;

&lt;p&gt;2) Alternativamente installare &lt;a href="http://www.apachefriends.org/en/xampp-linux.html"&gt;Xampp per Linux&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entreremo nei dettagli dell'applicazione la prossima volta.&lt;/p&gt;

&lt;p&gt;Nel frattempo a chi non abbia grande familiarità con il framework, ma volesse comunque seguire gli articoli, consiglierei di leggere almeno la guida &lt;a href="http://www.symfony-project.org/getting-started/1_2/it/"&gt;Getting started with symfony&lt;/a&gt; (in italiano).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3455985464617402342?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Os3fXva43LnTePX1pOKGcPCO8i8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Os3fXva43LnTePX1pOKGcPCO8i8/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/Os3fXva43LnTePX1pOKGcPCO8i8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Os3fXva43LnTePX1pOKGcPCO8i8/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/zaFdSt7ZbkA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3455985464617402342/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/introduzione-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3455985464617402342?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3455985464617402342?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/introduzione-symfony.html" title="Introduzione a 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;AkIHSH86fCp7ImA9WxNQFkw.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3180649904079753181</id><published>2009-09-22T13:22:00.001+02:00</published><updated>2009-09-22T13:22:19.114+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-22T13:22:19.114+02:00</app:edited><title>Programma per l'autunno</title><content type="html">&lt;p&gt;Come si può notare dall'archivio non ho avuto molto tempo di scrivere sul blog ultimamente, ma con l'arrivo dell'Autunno mi propongo di aumentare la frequenza degli articoli. Gli argomenti saranno in parte diversi da quelli trattati fino ad ora, un po' perché mi piace cambiare un po' perché scrivo sul blog prevalentemente di quello di cui mi occupo al momento.&lt;/p&gt;

&lt;p&gt;In particolare ho in programma una serie di brevi articoli su &lt;a href="http://www.symfony-project.org/"&gt;symfony&lt;/a&gt;, un framework &lt;strong&gt;PHP&lt;/strong&gt; basato sul design pattern &lt;strong&gt;Model View Controller&lt;/strong&gt; di cui ho avuto modo di approfondire la conoscenza negli ultimi mesi.&lt;/p&gt;

&lt;p&gt;Vorrei provare a realizzare una guida pratica 'a puntate' sviluppando con symfony, pezzo per pezzo, un'applicazione reale,  inizialmente molto semplice, ma che possa essere migliorata e arricchita di funzionalità nel tempo. Magari grazie anche ai suggerimenti e, perché no, al codice di qualche lettore interessato a contribuire.&lt;/p&gt;

&lt;p&gt;L'idea, lo dico subito, non è del tutto originale in quanto il più conosciuto dei tutorial di simfony (&lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/"&gt;Jobeet&lt;/a&gt;) è costituito da una serie di articoli inizialmente pubblicati sul blog ufficiale, attraverso i quali viene sviluppata una applicazione completa.&lt;/p&gt;

&lt;p&gt;Oltre ovviamente ad utilizzare una diversa applicazione di esempio, vorrei non pormi limiti di tempo e portare avanti lo sviluppo fino a che sarà divertente ed interessante farlo. L'intenzione non è certo quella di duplicare la documentazione del framework che è esauriente e disponibile anche in italiano, ma piuttosto quella di fornire qualche esempio pratico di codice. Maggiori dettagli prossimamente.&lt;/p&gt;

&lt;p&gt;Naturalmente se ce ne sarà l'occasione scriverò di ancora di Joomla, Drupal, Javascript, PHP ma con minore frequenza rispetto al passato.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3180649904079753181?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/PyU2m1E53w_MEiWc6EG_IvGZ7SU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PyU2m1E53w_MEiWc6EG_IvGZ7SU/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/PyU2m1E53w_MEiWc6EG_IvGZ7SU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PyU2m1E53w_MEiWc6EG_IvGZ7SU/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/vfEMzq4WaCc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3180649904079753181/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/programma-per-lautunno.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3180649904079753181?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3180649904079753181?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/09/programma-per-lautunno.html" title="Programma per l'autunno" /><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;CUIGQn4ycSp7ImA9WxJVGEQ.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8631839243012566709</id><published>2009-07-06T17:11:00.001+02:00</published><updated>2009-07-06T17:18:43.099+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-06T17:18:43.099+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Joomla" /><title>Rilasciato QContacts 1.0.6</title><content type="html">&lt;p&gt;Dopo un bel po' di tempo rilascio una nuova versione di &lt;strong&gt;QContacts&lt;/strong&gt;. Le novità sono solo due, spero gradite (almeno la prima mi è stata richiesta parecchie volte).&lt;/p&gt;

&lt;p&gt;Dopo l'invio del modulo è adesso possibile reindirizzare l'utente ad una pagina separata contenente un messaggio personalizzabile di conferma o ringraziamento. Il modo di funzionamento precedente, cioè mostrare il messaggio di ringraziamento in testa alla stessa pagina con il modulo è comunque sempre disponibile.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SlIVpS7qo7I/AAAAAAAAAGI/okAfRiu6HTA/s1600-h/qcontacts-grazie.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 351px; height: 132px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SlIVpS7qo7I/AAAAAAAAAGI/okAfRiu6HTA/s400/qcontacts-grazie.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5355366706097857458" /&gt;&lt;/a&gt;
&lt;p&gt;È disponibile un nuovo tipo di menù per mostrare una pagina con i link a tutte o solo alcune delle categorie contatti.&lt;/p&gt;

&lt;p&gt;La documentazione sul sito non è aggiornata alla nuova versione. Brevi istruzioni sono disponibili in &lt;a href="http://forum.latenight-coding.com/viewtopic.php?f=6&amp;t=406"&gt;questo post&lt;/a&gt; sul forum.&lt;/p&gt;

&lt;p&gt;Il componente si scarica &lt;a href="http://www.latenight-coding.com/it/joomla-addons/qcontacts/scarica-qcontacts.html"&gt;da qui&lt;/a&gt;. Si può installare direttamente 'sopra' una vecchia versione senza disinstallarla e senza perdere i dati dei contatti.&lt;/p&gt;

&lt;p&gt;Sto già pensando a qualche altra funzione da aggiungere una ulteriore versione sarà disponibile presto.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8631839243012566709?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/EhNuNbIsBOo9OzOzdS2UySIDo_A/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EhNuNbIsBOo9OzOzdS2UySIDo_A/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/EhNuNbIsBOo9OzOzdS2UySIDo_A/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EhNuNbIsBOo9OzOzdS2UySIDo_A/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/hx6lUZW2__o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8631839243012566709/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/07/rilasciato-qcontacts-106.html#comment-form" title="3 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8631839243012566709?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8631839243012566709?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/07/rilasciato-qcontacts-106.html" title="Rilasciato QContacts 1.0.6" /><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/SlIVpS7qo7I/AAAAAAAAAGI/okAfRiu6HTA/s72-c/qcontacts-grazie.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></entry><entry gd:etag="W/&quot;C0YBRns5fCp7ImA9WxJXFk8.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6160648502830573306</id><published>2009-06-10T09:55:00.001+02:00</published><updated>2009-06-10T10:05:57.524+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-10T10:05:57.524+02:00</app:edited><title>Drupal blog, archivio mensile con Views</title><content type="html">&lt;p&gt;Aggiungo un altro pezzo al &lt;a href="http://demo.latenight-coding.com/drupal-blog/" title="Demo blog Drupal"&gt;blog dimostrativo&lt;/a&gt; realizzato con &lt;strong&gt;Drupal&lt;/strong&gt;. Un &lt;strong&gt;archivio mensile&lt;/strong&gt; dei post con un blocco nella colonna laterale che contiene i link alle pagine dell'archivio ed a fianco il contatore degli articoli pubblicati in quel mese: una cosa abbastanza tipica per un blog.&lt;/p&gt;

&lt;p&gt;Per fare questo esistono moduli dedicati però anche il modulo &lt;strong&gt;Views&lt;/strong&gt; contiene una view predefinita adatta allo scopo ed ho scelto di seguire questa seconda strada. La prima cosa da fare è scaricare &lt;a href="http://drupal.org/project/views" title="Modulo Drupal Views"&gt;Views&lt;/a&gt;, decomprimere il pacchetto in &lt;em&gt;sites/all/modules/&lt;/em&gt; ed attivarlo in &lt;em&gt;Amministra&lt;/em&gt; &gt;&gt; &lt;em&gt;Struttura del sito&lt;/em&gt; &gt;&gt; &lt;em&gt;Moduli&lt;/em&gt; come qualsiasi altro modulo. Poi si può procedere in questo modo.&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;Amministra&lt;/em&gt; &gt;&gt; &lt;em&gt;Struttura del sito&lt;/em&gt; &gt;&gt; &lt;em&gt;Viste&lt;/em&gt; dall'elenco delle viste fare click sul link &lt;em&gt;Attiva&lt;/em&gt; della vista predefinita &lt;strong&gt;archive&lt;/strong&gt; (dovrebbe essere la prima della lista) .&lt;/p&gt;

&lt;p&gt;A questo punto in &lt;em&gt;Amministra&lt;/em&gt; &gt;&gt; &lt;em&gt;Struttura del sito&lt;/em&gt; &gt;&gt; &lt;em&gt;Blocchi&lt;/em&gt; è presente un blocco &lt;em&gt;Archive list&lt;/em&gt;. Basta assegnarlo al settore del tema che si preferisce: nel mio caso, tema &lt;strong&gt;Light Fantastic&lt;/strong&gt;, il settore è &lt;em&gt;Left navigation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Il blocco è ora visibile. Noterete che i mesi sono ordinati in modo crescente, mentre in genere in un blog si preferisce l'ordine inverso, con i mesi più recenti in alto. Cambiare l'ordinamento dovrebbe essere semplice, ma devo ammettere che la prima volta che l'ho fatto mi sono impallato anche perché l'interfaccia di &lt;strong&gt;Views&lt;/strong&gt; a mio parere non è che sia molto intuitiva. Comunque la soluzione era nel forum di supporto del modulo e questa è la procedura da seguire.&lt;/p&gt;
 
&lt;p&gt;Ritornati in &lt;em&gt;Amministra&lt;/em&gt; &gt;&gt; &lt;em&gt;Struttura del sito&lt;/em&gt; &gt;&gt; &lt;em&gt;Viste&lt;/em&gt;, fare click su link &lt;em&gt;Modifica&lt;/em&gt; della vista &lt;strong&gt;archive&lt;/strong&gt;. Nella schermata succcessiva, fare click sul link &lt;em&gt;Block&lt;/em&gt; nella colonna a sinistra (il riquadro &lt;em&gt;Block&lt;/em&gt; viene evidenziato con un colore di sfondo più chiaro); poi fare click su &lt;em&gt;Node: Creato anno + mese&lt;/em&gt; nel riquadro &lt;em&gt;Argomenti&lt;/em&gt;.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/Si9h0kYMXaI/AAAAAAAAAFg/U71UeQfss5s/s1600-h/drupal_views1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 91px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/Si9h0kYMXaI/AAAAAAAAAFg/U71UeQfss5s/s320/drupal_views1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345598838458310050" /&gt;&lt;/a&gt;
&lt;p&gt;Se avete una bassa risoluzione verticale sembra che non succeda niente, ma se scorrete in basso troverete un riquadro dal titolo &lt;em&gt;Defaults: Configura Argomento Node: Creato anno + mese&lt;/em&gt;. Modificare l'opzione &lt;em&gt;Azione da intraprendere se l'argomento non è presente&lt;/em&gt; in &lt;em&gt;Sommario, ordinato in modo decrescente&lt;/em&gt;. Premete &lt;em&gt;Aggiorna&lt;/em&gt;. Nei 2 riquadri successivi continuate a premere &lt;em&gt;Aggiorna&lt;/em&gt; senza fare modifiche, alla fine &lt;em&gt;Salva&lt;/em&gt;.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9iFOMJhkI/AAAAAAAAAFo/p4WqTdpMtCk/s1600-h/drupal_views2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 118px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9iFOMJhkI/AAAAAAAAAFo/p4WqTdpMtCk/s320/drupal_views2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345599124559988290" /&gt;&lt;/a&gt;
&lt;p&gt;L'ordinamento dei link nel blocco dovrebbe essere a posto, però c'è un altro problema se lo si vuole considerare tale: la view include tutti i nodi quindi ci troviamo nell'archivio anche i link inseriti nel blogroll (che ricordo sono nodi creati dal modulo &lt;strong&gt;weblinks&lt;/strong&gt;, vedere &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/04/blog-drupal-creare-un-blogroll-con.html" title="Blog Drupal, creare un blogroll con Weblinks"&gt;questo post&lt;/a&gt;). A me questa cosa non piace, chi la pensa allo stesso modo può modificare ulteriormente la view.&lt;/p&gt;

&lt;p&gt;Rimanendo in &lt;em&gt;Amministra&lt;/em&gt; &gt;&gt; &lt;em&gt;Struttura del sito &lt;/em&gt;&gt;&gt; &lt;em&gt;Viste&lt;/em&gt; fare click su &lt;em&gt;Defaults&lt;/em&gt; nella colonna sinistra, poi sul segno più (aggiungi) nel riquadro &lt;em&gt;Filtri&lt;/em&gt;.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9iTh8XeiI/AAAAAAAAAFw/ix0aRoiJ1-8/s1600-h/drupal_views3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 66px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9iTh8XeiI/AAAAAAAAAFw/ix0aRoiJ1-8/s320/drupal_views3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345599370380671522" /&gt;&lt;/a&gt;
&lt;p&gt;Nel riquadro che compare in basso &lt;em&gt;Defaults: Aggiungi filtri&lt;/em&gt; spuntare l'opzione &lt;em&gt;Node: tipo&lt;/em&gt; e premere il pulsante &lt;em&gt;Aggiungi&lt;/em&gt;.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9ij8PmAhI/AAAAAAAAAF4/MWbE9YCyQUU/s1600-h/drupal_views4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 182px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/Si9ij8PmAhI/AAAAAAAAAF4/MWbE9YCyQUU/s320/drupal_views4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345599652318544402" /&gt;&lt;/a&gt;
&lt;p&gt;Si apre un altro riquadro, &lt;em&gt;&lt;em&gt;Defaults: Configura filtro Node: Tipo&lt;/em&gt;&lt;/em&gt;. Selezionare &lt;em&gt;è uno di&lt;/em&gt; come &lt;em&gt;Operatore&lt;/em&gt; e spuntare &lt;em&gt;Intervento del blog&lt;/em&gt; come &lt;em&gt;Tipo di nodo&lt;/em&gt;. Premere &lt;em&gt;Aggiorna&lt;/em&gt; e poi &lt;em&gt;Salva&lt;/em&gt;.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/Si9i1pGsA1I/AAAAAAAAAGA/Uwrp_BOs0wM/s1600-h/drupal_views5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/Si9i1pGsA1I/AAAAAAAAAGA/Uwrp_BOs0wM/s320/drupal_views5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345599956418560850" /&gt;&lt;/a&gt;

&lt;p&gt;Il riquadro filtri deve ora mostrare:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;Node: Pubblicato Si &lt;/li&gt;
&lt;li&gt;Node: Tipo = Intervento del blog&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Con questa modifica solo i post saranno inclusi nell'archivio.&lt;/p&gt;

&lt;p&gt;Il risultato di tutto il lavoro è visibile sul sito demo&lt;/p&gt;

&lt;a href="http://demo.latenight-coding.com/drupal-blog/" title="Demo blog Drupal"&gt;http://demo.latenight-coding.com/drupal-blog&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6160648502830573306?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/gR-nFIemUDo94yKHQtgN5-xJPj8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/gR-nFIemUDo94yKHQtgN5-xJPj8/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/gR-nFIemUDo94yKHQtgN5-xJPj8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/gR-nFIemUDo94yKHQtgN5-xJPj8/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/DtB0qYaWyQk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6160648502830573306/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/06/drupal-blog-archivio-mensile-con-views.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6160648502830573306?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6160648502830573306?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/06/drupal-blog-archivio-mensile-con-views.html" title="Drupal blog, archivio mensile con Views" /><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/Si9h0kYMXaI/AAAAAAAAAFg/U71UeQfss5s/s72-c/drupal_views1.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;DkQGRH87eCp7ImA9WxJREkw.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-2514177453913214971</id><published>2009-05-13T13:29:00.001+02:00</published><updated>2009-05-13T13:32:05.100+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-13T13:32:05.100+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Joomla" /><title>YOOTheme ZOO, gestione cataloghi per Joomla</title><content type="html">&lt;p&gt;&lt;a href="http://zoo.yootheme.com/" title="YOOTheme ZOO"&gt;YOOTheme ZOO&lt;/a&gt; fa parte, insieme a &lt;a href="http://k2.joomlaworks.gr/" tile="Estensione Joomla K2"&gt;K2&lt;/a&gt; ed altre estensioni per Joomla 1.5, di quel gruppo di estensioni di terze parti sviluppate per sopperire a due ben note limitazioni del CMS: l'impossibilità di creare nativamente &lt;strong&gt;tipi di contenuto&lt;/strong&gt; aggiuntivi con campi personalizzati e la mancanza di livelli ulteriori rispetto a &lt;strong&gt;sezione e categoria&lt;/strong&gt; per la categorizzazione dei contenuti.&lt;/p&gt;

&lt;p&gt;ZOO è distribuito in una versione &lt;strong&gt;Lite&lt;/strong&gt; gratuita ed una versione &lt;strong&gt;Pro&lt;/strong&gt; a pagamento.&lt;/p&gt;

&lt;p&gt;L'estensione ci permette di creare &lt;strong&gt;cataloghi&lt;/strong&gt;, &lt;strong&gt;articoli&lt;/strong&gt; (&lt;em&gt;items&lt;/em&gt;) e &lt;strong&gt;tipi di contenuto&lt;/strong&gt;. Per ogni catalogo possiamo definire un "albero" di categorie e sottocategorie nidificate; per ogni tipo di contenuto possiamo creare un insieme di campi compilabili tramite &lt;strong&gt;elementi HTML&lt;/strong&gt; (text, textarea, checkbox, radio, select), ma anche campi contenenti immagini (anche versione Lite), video, gallerie e file destinati al download (solo versione Pro).&lt;/p&gt;

&lt;p&gt;In fase di creazione, un articolo può essere inserito in uno o più cataloghi e in una o più categorie.&lt;/p&gt;

&lt;p&gt;Dalla gestione standard dei menù è quindi possibile creare voci collegate ad un &lt;strong&gt;singolo articolo&lt;/strong&gt; o ad una &lt;strong&gt;categoria&lt;/strong&gt;. Inoltre tramite il modulo &lt;strong&gt;ZOO Menu&lt;/strong&gt; si possono creare automaticamente le voci di menù per navigare un intero catalogo.&lt;/p&gt;

&lt;p&gt;L'aspetto di articoli e categorie è personalizzabile con l'uso di template: la versione Lite include un solo template base (&lt;em&gt;Article&lt;/em&gt;), la versione Pro anche i template &lt;em&gt;Blog&lt;/em&gt;, &lt;em&gt;Product&lt;/em&gt; e &lt;em&gt;Download&lt;/em&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-2514177453913214971?l=sviluppare-in-rete.blogspot.com'/&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/WOdGI4Xd6XPF8l8pxepQfzf0omc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WOdGI4Xd6XPF8l8pxepQfzf0omc/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/WOdGI4Xd6XPF8l8pxepQfzf0omc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WOdGI4Xd6XPF8l8pxepQfzf0omc/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/l2WvDE3va9I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/2514177453913214971/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/05/yootheme-zoo-gestione-cataloghi-per.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2514177453913214971?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2514177453913214971?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/05/yootheme-zoo-gestione-cataloghi-per.html" title="YOOTheme ZOO, gestione cataloghi per Joomla" /><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>
