<?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" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;DkYCQXs7fSp7ImA9WhRRFUo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007</id><updated>2011-11-29T14:42:40.505+01:00</updated><category term="AJAX" /><category term="PHP" /><category term="Drupal" /><category term="Strumenti" /><category term="Textpattern" /><category term="Symfony2" /><category term="symfony" /><category term="Mootools" /><category term="Joomla" /><category term="Javascript" /><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="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><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>110</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/sviluppare-in-rete" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="sviluppare-in-rete" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><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><entry gd:etag="W/&quot;Ak4DRXc7cSp7ImA9WhZREEo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4581564624151160157</id><published>2011-04-06T10:34:00.000+02:00</published><updated>2011-04-06T10:36:14.909+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-06T10:36:14.909+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Symfony2" /><title>Symfony2 PR10 e FooAppsHelloBundle</title><content type="html">&lt;p&gt;Ho dovuto annullare le modifiche fatte al codice del bundle di esempio (&lt;em&gt;FooAppsHelloBundle&lt;/em&gt;) in seguito del rilascio di Symfony2 Standard Edition PR9. Cosa è successo si può vedere dal &lt;a href="https://github.com/mgiagnoni/HelloBundle/commit/0c8d665af75628072d3821961f1fd5195df32231"&gt;diff su GitHub&lt;/a&gt;: il suffisso 'Bundle' era stato rimosso dal nome del bundle (in alcuni contesti), ma la cosa non è piaciuta a molti e con la versione PR10 si è tornati alla situazione precedente. Chiaramente visto che non esiste retrocompatibilità tra diverse versioni preview di Symfony2, il bundle non funziona più su Symfony PR9. Il testo degli articoli precedenti riporta la sintassi attuale e corretta.&lt;/p&gt;

&lt;p&gt;Si può entrare a questo punto un po' più in dettaglio sul funzionamento del bundle di esempio. Solo, non oggi.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4581564624151160157?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/woP5XeFcaNhZy2K1dVWHo2pFgDc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/woP5XeFcaNhZy2K1dVWHo2pFgDc/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/woP5XeFcaNhZy2K1dVWHo2pFgDc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/woP5XeFcaNhZy2K1dVWHo2pFgDc/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/0yYyZ8iXUhM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4581564624151160157/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/04/symfony2-pr10-e-fooappshellobundle.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4581564624151160157?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4581564624151160157?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/04/symfony2-pr10-e-fooappshellobundle.html" title="Symfony2 PR10 e FooAppsHelloBundle" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkYHRXc-eyp7ImA9WhZXEkk.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-273595478490996553</id><published>2011-03-25T15:26:00.001+01:00</published><updated>2011-05-01T10:55:34.953+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-01T10:55:34.953+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Utilizzare fixtures in Symfony2, Doctrine2</title><content type="html">&lt;p&gt;Articolo modificato il &lt;em&gt;1 Maggio 2011&lt;/em&gt; per far funzionare il tutto con la versione &lt;strong&gt;Beta1&lt;/strong&gt; e, si spera, successive di Symfony2.&lt;/p&gt;
&lt;p&gt;Ho voluto provare ad implementare il caricamento di &lt;em&gt;fixtures&lt;/em&gt; in &lt;em&gt;FooAppsHelloBundle&lt;/em&gt; per consentire ai più pigri di avere una lista predefinita di amici da salutare senza bisogno di inserirli manualmente. La prima cosa da dire è che in Symfony2 non viene più supportato il caricamento di fixtures da file YAML come in symfony1, bisogna quindi aggiungere al proprio bundle una classe che svolga questo compito.&lt;/p&gt;

&lt;p&gt;Per chi, come ho fatto io per questi primi esperimenti, utilizza Symfony &lt;em&gt;Standard Edition&lt;/em&gt; si presenta un ulteriore problema in quanto nella distribuzione non è stata inclusa l'estensione di Doctrine2 necessaria. Per prima cosa dalla cartella del progetto si deve eseguire questo comando&lt;/p&gt;

&lt;pre&gt;
git clone git://github.com/doctrine/data-fixtures.git vendor/doctrine-data-fixtures
&lt;/pre&gt;

&lt;p&gt;Chi non ha git (ma converrebbe iniziare ad imparare ad usarlo), può ottenere il pacchetto da &lt;a href="https://github.com/doctrine/data-fixtures"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Per le ragioni viste nell'articolo precedente bisogna anche registrare il namespace&lt;/p&gt;

&lt;pre&gt;
// app/autoload.php

/* ... */
$loader-&amp;gt;registerNamespaces(array(
    'Symfony'            =&amp;gt; array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
    'Sensio'             =&amp;gt; __DIR__.'/../vendor/bundles',
    'JMS'                =&amp;gt; __DIR__.'/../vendor/bundles',
    /* Aggiungere la riga seguente */
    'Doctrine\\Common\\DataFixtures'   =&amp;gt; __DIR__.'/../vendor/doctrine-data-fixtures/lib',
    /* ... */
));
&lt;/pre&gt;

&lt;p&gt;A partire dalla versione &lt;strong&gt;Beta&lt;/strong&gt;1 di Symfony2, il comando per il caricamento di fixtures si trova in un bundle separato &lt;strong&gt;DoctrineFixturesBundle&lt;/strong&gt; che bisogna installare perché non incluso nella versione &lt;em&gt;Standard&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;
git clone git://github.com/symfony/DoctrineFixturesBundle.git vendor/bundles/Symfony/Bundle/DoctrineFixturesBundle
&lt;/pre&gt;

&lt;p&gt;Il bundle va poi registrato&lt;/p&gt;

&lt;pre&gt;
// app/AppKernel.php

    public function registerBundles()
    {
        return array(
            /* ... */
            new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),
        );
    }
&lt;/pre&gt;

&lt;p&gt;Si può a questo punto creare la nostra classe per il caricamento delle fixtures.&lt;/p&gt;

&lt;pre&gt;
// src/FooApps/HelloBundle/Fixtures/ORM/HelloFixtures.php

namespace FooApps\HelloBundle\Fixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use FooApps\HelloBundle\Entity\Friend;

class HelloFixtures implements FixtureInterface
{
    public function load($manager)
    {
        $friend = new Friend();
        $friend-&amp;gt;setName('Fabien');
        $manager-&amp;gt;persist($friend);

        /* Creare altre istanze di Friend qui se necessario */

        $manager-&amp;gt;flush();
    }
}
&lt;/pre&gt;

&lt;p&gt;Cosa succede nel metodo &lt;strong&gt;load()&lt;/strong&gt; dovrebbe essere abbastanza chiaro: si creano le istanze dell'entità &lt;strong&gt;Friend&lt;/strong&gt;, se ne impostano le proprietà,  se ne chiede la persistenza sul database all'&lt;em&gt;entity manager&lt;/em&gt;, infine &lt;strong&gt;flush()&lt;/strong&gt; (notare che viene invocato una sola volta alla fine) salva fisicamente i dati sul database.&lt;/p&gt;

&lt;p&gt;Fatto tutto questo è possibile usare il seguente comando&lt;/p&gt;

&lt;pre&gt;php app/console doctrine:fixtures:load --fixtures=src/FooApps/HelloBundle/DataFixtures/ORM&lt;/pre&gt;

&lt;p&gt;Se si omette l'opzione &lt;em&gt;--fixtures&lt;/em&gt;, saranno caricate con un solo comando le fixtures di tutti i bundle del progetto per i quali sia definita una classe loader.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-273595478490996553?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/RuuHjLcSaDm1Wdb-ONk6HQaMGn8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RuuHjLcSaDm1Wdb-ONk6HQaMGn8/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/RuuHjLcSaDm1Wdb-ONk6HQaMGn8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/RuuHjLcSaDm1Wdb-ONk6HQaMGn8/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/IFJdS8B3ZXo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/273595478490996553/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/utilizzare-fixtures-in-symfony2.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/273595478490996553?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/273595478490996553?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/utilizzare-fixtures-in-symfony2.html" title="Utilizzare fixtures in Symfony2, Doctrine2" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;C0MFR306cSp7ImA9WhZTGEo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4468691596814591000</id><published>2011-03-23T10:10:00.000+01:00</published><updated>2011-03-23T10:10:16.319+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-23T10:10:16.319+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Symfony2, installare un bundle</title><content type="html">&lt;p&gt;Penso possa essere utile, anche solo come promemoria, indicare i passi per installare un bundle nella propria applicazione utilizzando come esempio &lt;a href="https://github.com/mgiagnoni/HelloBundle"&gt;FooAppsHelloBundle&lt;/a&gt; già presentato negli articoli precedenti. Presupponendo di avere già installato e configurato Symfony Standard Edition, la prima cosa da fare è scaricare il codice sorgente. Ci sono due modi per farlo: se si ha git installato basta posizionarsi nella cartella del proprio progetto ed usare questo comando&lt;/p&gt;

&lt;pre&gt;
git clone git://github.com/mgiagnoni/HelloBundle.git src/FooApps/HelloBundle
&lt;/pre&gt;
&lt;p&gt;Git creerà la cartella &lt;em&gt;src/FooApps/HelloBundle&lt;/em&gt; e vi copierà il contenuto del repository.&lt;/p&gt;

&lt;p&gt;Se no dalla pagina principale del bundle su GitHub con il pulsante Downloads scarichiamo un archivio compresso del sorgente, creiamo la cartella &lt;em&gt;src/FooApps&lt;/em&gt; nel nostro progetto e ci decomprimiamo l'archivio: sarà creata una cartella &lt;em&gt;mgiagnoni-HelloBundle-7cc65167&lt;/em&gt; (la parte finale cambia perché è l'hash dell'ultimo commit) che va rinominata in &lt;em&gt;HelloBundle&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ci si potrebbe chiedere la ragione di queste due cartelle nidificate (&lt;em&gt;FooApps/HelloBundle&lt;/em&gt;). Symfony utilizza un sistema standard per l'&lt;em&gt;autoloading&lt;/em&gt; delle classi che richiede che il percorso relativo di un file contenente la dichiarazione di una classe sia ricavabile dal nome qualificato (che include cioè il namespace) della classe stessa (&lt;a href="http://groups.google.com/group/php-standards/web/psr-0-final-proposal"&gt;maggiori dettagli&lt;/a&gt; per chi è interessato).&lt;/p&gt;

&lt;p&gt;Nome qualificato della classe del nostro bundle: FooApps\HelloBundle\FooAppsHelloBundle
Percorso relativo del file: FooApps/HelloBundle/FooAppsHelloBundle.php&lt;/p&gt;

&lt;p&gt;Possiamo facilmente verificare che anche il bundle di esempio &lt;em&gt;AcmeDemoBundle&lt;/em&gt; installato con Symfony Standard Edition è organizzato nello stesso modo.&lt;/p&gt;

&lt;p&gt;Chiaramente a questo punto ci deve essere un sistema per far sapere alla funzione di autoload il percorso assoluto della cartella che corrisponde al namespace di primo livello (nome del vendor, &lt;em&gt;FooApps&lt;/em&gt; nel nostro esempio). Questo si fa registrando il namespace nel file &lt;em&gt;app/autoload.php&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
// app/autoload.php

$loader-&gt;registerNamespaces(array(
    /* ... */
    'FooApps' =&gt; __DIR__.'/../src',
);
&lt;/pre&gt;

&lt;p&gt;L'operazione è necessaria ogni volta che si crea un nuovo vendor. Se avessi creato un &lt;em&gt;AcmeHelloBundle&lt;/em&gt; sfruttando il vendor del bundle dimostrativo mi sarei risparmiato questo passaggio, così come a questo punto me lo risparmierei se creassi all'interno dello stesso progetto altri bundle sotto il vendor &lt;em&gt;FooApps&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Se si apre il file si noterà nella parte che sopra ho omesso la registrazione di una serie di namespace del framework e di altre componenti. Modificando i percorsi predefiniti delle cartelle dove vengono cercate le classi aventi un determinato namespace di primo livello, sarà possibile ad esempio creare una installazione di Symfony condivisa tra più progetti.&lt;/p&gt;

&lt;p&gt;Va poi registrato il bundle&lt;/p&gt;

&lt;pre&gt;
// app/AppKernel.php

public function registerBundles()
{
    return array(
        /* ... */
        new FooApps\HelloBundle\FooAppsHelloBundle(),
    );
}
&lt;/pre&gt;

&lt;p&gt;Il bundle definisce delle rotte che vanno importate nella configurazione del progetto.&lt;/p&gt;

&lt;pre&gt;
# app/config/routing.yml

fooapps_hello:
    resource: "@FooAppsHelloBundle/Resources/config/routing.yml"
    prefix: /hello_friend
&lt;/pre&gt;

&lt;p&gt;Il bundle utilizza Doctrine per cui deve essere inserito nel parametro &lt;i&gt;mappings&lt;/i&gt; della configurazione dell'orm (rimando maggiori dettagli su questo ad un'altra volta).&lt;/p&gt;

&lt;pre&gt;
# app/config/config.yml

# ...
doctrine:
        # ...
    orm:
        # ...
        default_entity_manager: default
        entity_managers:
            default:
                mappings:
                    # ...
                    FooAppsHelloBundle: ~
&lt;/pre&gt;

&lt;p&gt;Infine bisogna creare database e tabella dove saranno salvati gli oggetti &lt;strong&gt;Friend&lt;/strong&gt;. Dalla  cartella del progetto si esegue&lt;/p&gt;

&lt;pre&gt;
$ php app/console doctrine:database:create
$ php app/console doctrine:schema:create
&lt;/pre&gt;

&lt;p&gt;Questi due comandi richiedono che si sia eseguita la procedura di configurazione di Symfony Standard inserendo nome database, nome utente database e password (si può anche editare direttamente il file &lt;em&gt;app/config/parameters.ini&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Come si vede c'è un po' di lavoro manuale, ma è possibile che prima o poi sia creato qualcosa che automatizzi almeno in parte la procedura.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4468691596814591000?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_-p5PFWpbE-ohS-UHNUABOA2C4U/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_-p5PFWpbE-ohS-UHNUABOA2C4U/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/_-p5PFWpbE-ohS-UHNUABOA2C4U/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_-p5PFWpbE-ohS-UHNUABOA2C4U/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/cAnc5EAIDHY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4468691596814591000/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/symfony2-installare-un-bundle.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4468691596814591000?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4468691596814591000?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/symfony2-installare-un-bundle.html" title="Symfony2, installare un bundle" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkECSX08fCp7ImA9WhZTF04.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1671121278583294185</id><published>2011-03-21T20:10:00.000+01:00</published><updated>2011-03-21T20:11:08.374+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-21T20:11:08.374+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Symfony2, struttura di un bundle</title><content type="html">&lt;p&gt;Come ho accennato nell'articolo precedente, in &lt;strong&gt;Symfony2&lt;/strong&gt; i bundle svolgono le funzioni di applicazioni, moduli e plugin di symfony 1.x, quindi le nostre applicazioni saranno organizzate in bundle come lo è del resto il framework stesso.&lt;/p&gt;

&lt;p&gt;Chi proviene dalla versione (verrebbe da dire precedente, ma è ancora l'attuale) di Symfony avrà quasi sicuramente letto il tutorial &lt;strong&gt;Jobeet&lt;/strong&gt;. Se ricordate, in tutta la prima parte il codice è organizzato in applicazioni (frontend, backend) e moduli. Poi il &lt;a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/it/20"&gt;giorno 20&lt;/a&gt;, vengono presentati i plugin e suggerito un nuovo modo di organizzare il codice trasformando tutto quello che era stato fatto fino a quel momento in un &lt;em&gt;sfJobeetPlugin&lt;/em&gt;: un casino di file spostati, classi rinominate e trasformate in astratte. Ricordo che a metà del prendi di qui sposta di là, rinomina questo e quello mi scaricai da SVN il risultato finale della giornata e festa finita, e non credo di essere stato l'unico.&lt;/p&gt;

&lt;p&gt;Alla fine ti può convincere l'idea che organizzare anche il proprio codice con i plugin sia la soluzione più pulita e lineare. Però se ti ci fanno arrivare in questo modo, quasi alla fine del tutorial, dopo che magari si è iniziato a fare i primi esperimenti di sviluppo con frontend backend e moduli, è facile continuare ad utilizzare la struttura che si è appresa per prima. Almeno così è successo a me.&lt;/p&gt;

&lt;p&gt;Se dovesse esistere qualcosa di simile a Jobeet per Symfony2 non c'è da aspettarsi questo tipo di sorprese. I bundle vengono presentati fino da subito con l'esempio 'Hello world!' e con i bundle si continua.&lt;/p&gt;

&lt;p&gt;La struttura elementare di un bundle si capisce anche solo guardando l'organizzazione dei file nel repository di &lt;a href="https://github.com/mgiagnoni/HelloBundle"&gt;FooAppsHelloBundle&lt;/a&gt; anche se vedremo che le cose possono diventare molto più complesse di così.&lt;/p&gt;

&lt;pre&gt;
Controller
  -- DefaultController.php (2)
  -- FriendController.php (2)
Entity
  -- Friend.php (3)
Form
  -- FriendForm.php (4)
Resources
  -- config
  -- -- doctrine
  -- -- -- metadata
  -- -- -- -- orm
  -- -- -- -- -- FooApps.HelloBundle.Entity.Friend.dcm.yml (5)
  -- -- routing.yml (6)
  -- -- validation.yml (7)
  -- doc
  -- -- index.rst (8)
  -- meta
  -- -- LICENSE
  -- views (9)
FooAppsHelloBundle.php (1)
&lt;/pre&gt;

&lt;p&gt;(1) Contiene la dichiarazione della classe del bundle. Il nome del file e della classe devono seguire una precisa convenzione (in realtà ce ne sono più di una, quella seguita nell'esempio è la più comune):&lt;/p&gt;

&lt;p&gt;Nome Vendor (FooApps) + nome bundle (Hello) + suffisso Bundle&lt;/p&gt;

&lt;p&gt;Se si apre &lt;em&gt;FooAppsHelloBundle.php&lt;/em&gt; su GitHub si può notare che la classe si limita ad estendere una classe del framework senza contenere codice specifico ed è dichiarata all'interno di un &lt;a href="http://www.php.net/manual/en/language.namespaces.php"&gt;namespace&lt;/a&gt; (&lt;em&gt;FooApps\HelloBundle&lt;/em&gt;) anch'esso costruito seguendo una convenzione precisa:&lt;/p&gt;

&lt;p&gt;Nome Vendor \ nome bundle + suffisso Bundle&lt;/p&gt;

&lt;p&gt;L'esistenza di questa classe e relativo namespace sono il requisito minimo perché si possa registrare un bundle (vedremo prossimamente installazione e registrazione dei bundle). Vero è che un bundle che si limitasse a questo servirebbe a poco, neppure a dire 'Ciao!'.&lt;/p&gt;

&lt;p&gt;Accenno brevemente agli altri file perché quasi tutti meritano una trattazione approfondita che sarà fatta nelle prossime puntate.&lt;/p&gt;

&lt;p&gt;(2) Controller, il nostro bundle ne usa due. Ogni classe controller ha dei metodi pubblici detti azioni.&lt;/p&gt;
&lt;p&gt;(3) Entità, a noi serve solo una entità &lt;em&gt;Friend&lt;/em&gt;. Un'istanza di questa classe è un oggetto di cui possiamo ottenere la persistenza nel database tramite l'orm (Doctrine2). La classe è generata a partire dalle informazioni contenute nel file YAML (5). Vedremo che ci sono diversi modi di definire e generare le entità.&lt;/p&gt;
&lt;p&gt;(4) Classe del form per la creazione e modifica di un oggetto &lt;em&gt;Friend&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;(6) Definizioni delle rotte.&lt;/p&gt;
&lt;p&gt;(7) Regole di validazione dell'entità &lt;em&gt;Friend&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;(8) Documentazione.&lt;/p&gt;
&lt;p&gt;(9) Template. L'esempio utilizza il linguaggio &lt;strong&gt;Twig&lt;/strong&gt; per i template.&lt;/p&gt;

&lt;p&gt;Non poco per un "Hello world!" anche se più evoluto rispetto alla versione base. Vedremo tutto un po' per volta.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1671121278583294185?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ZRuAh2_eKMVNBtjOZ7PveBR6t2M/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZRuAh2_eKMVNBtjOZ7PveBR6t2M/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/ZRuAh2_eKMVNBtjOZ7PveBR6t2M/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ZRuAh2_eKMVNBtjOZ7PveBR6t2M/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/vaNlNMMFgdY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1671121278583294185/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/symfony2-struttura-di-un-bundle.html#comment-form" title="3 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1671121278583294185?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1671121278583294185?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/symfony2-struttura-di-un-bundle.html" title="Symfony2, struttura di un bundle" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;DUEGQXc6cSp7ImA9WhZTFU8.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1177305860451856858</id><published>2011-03-19T10:26:00.003+01:00</published><updated>2011-03-19T10:40:20.919+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-19T10:40:20.919+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><category scheme="http://www.blogger.com/atom/ns#" term="Symfony2" /><title>Un semplice bundle per iniziare con Symfony2</title><content type="html">&lt;p&gt;L'avvicinarsi della versione stabile di &lt;strong&gt;Symfony2&lt;/strong&gt; (ma c'è ancora un po' da aspettare, le ultime notizie danno il rilascio di una &lt;em&gt;Release Candidate&lt;/em&gt; ad inizio aprile), mi ha fatto decidere a scrivere un semplice &lt;strong&gt;bundle&lt;/strong&gt; sperimentale.&lt;/p&gt;

&lt;p&gt;Per chi non lo sapesse ancora, in Symfony2 non esistono più applicazioni, moduli e plugin, ma solo bundle.&lt;/p&gt;

&lt;p&gt;In particolare ho realizzato una evoluzione :) del bundle 'Hello world!' che si trova in Symfony2 &lt;em&gt;Standard Edition&lt;/em&gt; (un'altra novità, ora ci sono anche le edizioni o distribuzioni del framework). Anche il mio dice 'Ciao!', ma solo a chi vogliamo noi, non a chiunque: ci permette infatti di mantenere una lista di amici da salutare comodamente con un clic. L'esempio più semplice che mi sia venuto in mente per provare le operazioni di inserimento e modifica di dati tramite un form e il salvataggio degli stessi in un database con Doctrine2.&lt;/p&gt;

&lt;p&gt;Ho pure avuto il coraggio di metterlo su GitHub, &lt;a href="https://github.com/mgiagnoni/HelloBundle"&gt;FooAppsHelloBundle&lt;/a&gt;, con le istruzioni per installarlo se qualcuno lo volesse provare.&lt;/p&gt;

&lt;p&gt;Nella sua banalità sarà il punto di partenza per una serie di articoli introduttivi a Symfony2, che ho intenzione di iniziare a scrivere nei prossimi giorni.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1177305860451856858?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/7_zK1-z7Dtjp4S5sRbg_bNlafms/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7_zK1-z7Dtjp4S5sRbg_bNlafms/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/7_zK1-z7Dtjp4S5sRbg_bNlafms/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7_zK1-z7Dtjp4S5sRbg_bNlafms/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/CflHQHnO0xI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1177305860451856858/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/un-semplice-bundle-per-iniziare-con.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1177305860451856858?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1177305860451856858?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2011/03/un-semplice-bundle-per-iniziare-con.html" title="Un semplice bundle per iniziare con Symfony2" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;Ak8MQngzeip7ImA9WxFaF00.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3206576307043756557</id><published>2010-07-21T12:36:00.003+02:00</published><updated>2010-07-21T12:41:23.682+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-21T12:41:23.682+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Rilasciato lyMediaManagerPlugin 0.5.2</title><content type="html">&lt;p&gt;Ho continuato il lavoro estivo sul plugin &lt;a href="http://www.symfony-project.org/plugins/lyMediaManagerPlugin"&gt;lyMediaManagerPlugin&lt;/a&gt;. La versione 0.5.2 consente la possibilità di effettuare l'upload di un file (immagine o altro) e la creazione di una sottocartella direttamente dalla vista 'a icone'.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/TEbONh5LCNI/AAAAAAAAAJo/Ec9KKNStNDs/s1600/media-manager2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 199px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/TEbONh5LCNI/AAAAAAAAAJo/Ec9KKNStNDs/s320/media-manager2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5496307127084058834" /&gt;&lt;/a&gt;
&lt;p&gt;Chi avesse avuto voglia di provare la versione precedente può fare l'upgrade semplicemente con il comando&lt;/p&gt;

&lt;pre&gt;./symfony plugin:upgrade lyMediaManagerPlugin --stability=beta&lt;/pre&gt;

&lt;p&gt;L'opzione &lt;em&gt;stability&lt;/em&gt; è obbligatoria in quanto il plugin è in una versione beta.&lt;/p&gt;

&lt;p&gt;L'alternativa come al solito è il checkout dal repository Subversion seguendo le istruzioni presenti nel file README ed online.&lt;/p&gt;

&lt;p&gt;Tutto qui. Preferisco fare poche modifiche per volta e rilasciare nuove versioni con una certa frequenza.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3206576307043756557?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/3erPcCtR2_mSRNZE7avKwrGrtDY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/3erPcCtR2_mSRNZE7avKwrGrtDY/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/3erPcCtR2_mSRNZE7avKwrGrtDY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/3erPcCtR2_mSRNZE7avKwrGrtDY/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/kiZfCET1b1E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3206576307043756557/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/rilasciato-lymediamanagerplugin-052.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3206576307043756557?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3206576307043756557?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/rilasciato-lymediamanagerplugin-052.html" title="Rilasciato lyMediaManagerPlugin 0.5.2" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_unGpwtr1q-o/TEbONh5LCNI/AAAAAAAAAJo/Ec9KKNStNDs/s72-c/media-manager2.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0INQX46cSp7ImA9WxFaEE0.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3630512169231746437</id><published>2010-07-13T10:26:00.001+02:00</published><updated>2010-07-13T10:26:30.019+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-13T10:26:30.019+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>lyMediaManagerPlugin 0.5.1</title><content type="html">&lt;p&gt;Ho rilasciato la versione 0.5.1 beta del plugin &lt;a href="http://www.symfony-project.org/plugins/lyMediaManagerPlugin"&gt;lyMediaManager&lt;/a&gt;. La principale novità è costituita dal task 'synchronize' del tutto analogo a quello presente nel plugin &lt;strong&gt;sfAssetsLibrary&lt;/strong&gt;, che consente di sincronizzare la libreria di media con il contenuto di una cartella sul server. Tra le altre cose questo può essere utile per chi trovi più comodo caricare immagini o altri file via FTP piuttosto che tramite interfaccia web. Sintassi e parametri sono nel file README.&lt;/p&gt;

&lt;p&gt;Inoltre ho aggiunto la funzione di paginazione e reso ordinabili per nome o data i file nella visualizzazione a icone. Nella visualizzazione a lista questa funzione era già disponibile, perché fornita dall'admin generator, e attivabile nel modo standard con un click sull'intestazione della colonna.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/TDuu1yXE70I/AAAAAAAAAJg/6HT8kFnfWQY/s1600/media-manager.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 137px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/TDuu1yXE70I/AAAAAAAAAJg/6HT8kFnfWQY/s320/media-manager.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5493176409583316802" /&gt;&lt;/a&gt;
&lt;p&gt;A parte questo solo aggiustamenti vari.&lt;/p&gt;

&lt;p&gt;Nella visualizzazione a icone sono presenti  dei collegamenti che aprono i form per la creazione di una sottocartella o l'upload di un file in una pagina separata. Chi ha utilizzato sfAssetsLibrary avrà notato che queste operazioni si compiono senza lasciare la pagina tramite dei form 'a comparsa': è un'interfaccia più immediata che proverò a ricreare anche in lyMediaManager. Prossimamente.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3630512169231746437?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/y-6cmAUaBtHw_RpJ_bANmnVdpzw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/y-6cmAUaBtHw_RpJ_bANmnVdpzw/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/y-6cmAUaBtHw_RpJ_bANmnVdpzw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/y-6cmAUaBtHw_RpJ_bANmnVdpzw/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/dXpRqJSNcJo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3630512169231746437/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/lymediamanagerplugin-051.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3630512169231746437?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3630512169231746437?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/lymediamanagerplugin-051.html" title="lyMediaManagerPlugin 0.5.1" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/TDuu1yXE70I/AAAAAAAAAJg/6HT8kFnfWQY/s72-c/media-manager.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEICRH45fSp7ImA9WxFbFUU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-7688709241976946091</id><published>2010-07-08T12:47:00.002+02:00</published><updated>2010-07-08T12:56:05.025+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-08T12:56:05.025+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Un media manager per Lyra</title><content type="html">&lt;p&gt;In un CMS è necessario offrire agli utenti uno strumento per gestire facilmente una &lt;strong&gt;libreria di immagini&lt;/strong&gt; (ma anche altri file o documenti) da inserire negli articoli o comunque utilizzare per il proprio sito.&lt;/p&gt;

&lt;p&gt;Per Lyra ho cercato un plugin di symfony che offrisse questo tipo di funzionalità. Quello che mi è piaciuto di più è &lt;a href="http://www.symfony-project.org/plugins/sfAssetsLibraryPlugin"&gt;sfAssetsLibrary&lt;/a&gt; che consente la gestione degli upload dei file e la generazione automatica delle miniature delle immagini, ma permette anche di associare informazioni aggiuntive ad ogni file (titolo descrizione, autore, copyright). Solo che è per &lt;strong&gt;Propel&lt;/strong&gt;, mentre a me serviva per &lt;strong&gt;Doctrine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quindi ho iniziato lo sviluppo di un nuovo plugin &lt;strong&gt;lyMediaManager&lt;/strong&gt;, ispirato a sfAssetsLibrary anche se non un vero e proprio 'porting'. Per quanto sia pensato per essere incluso in Lyra è un plugin standard che può essere installato in qualsiasi applicazione symfony.&lt;/p&gt;

&lt;p&gt;Il lavoro da fare è ancora molto, è comunque disponibile una versione beta. Non mi dilungo oltre, il pacchetto include un file README con le istruzioni. Come per ogni altro plugin si può scegliere l'installazione tramite comando symfony 'plugin:install' oppure fare il checkout via SVN direttamente dal repository: la seconda strada permette di avere le ultime novità prima che siano 'impacchettate' nella successiva release, anche se al prezzo di una maggiore instabilità.&lt;/p&gt;

&lt;p&gt;Il plugin si trova sul repository ufficiale di symfony, il link per chi vuole darci un'occhiata è questo&lt;/p&gt;

&lt;a href="http://www.symfony-project.org/plugins/lyMediaManagerPlugin"&gt;www.symfony-project.org/plugins/lyMediaManagerPlugin&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-7688709241976946091?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/777N13oNnLgDmPk3BdbToQZDHV4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/777N13oNnLgDmPk3BdbToQZDHV4/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/777N13oNnLgDmPk3BdbToQZDHV4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/777N13oNnLgDmPk3BdbToQZDHV4/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/LNCEoPdQYEk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/7688709241976946091/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/un-media-manager-per-lyra.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/7688709241976946091?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/7688709241976946091?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/07/un-media-manager-per-lyra.html" title="Un media manager per Lyra" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0cFQ3g5fyp7ImA9WxFRE0U.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5036626081677682825</id><published>2010-04-27T19:16:00.001+02:00</published><updated>2010-04-27T19:23:32.627+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-27T19:23:32.627+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, gestire un profilo utente con sfGuard</title><content type="html">&lt;p&gt;Ad oggi la gestione utenti di Lyra è quasi inesistente. Viene utilizzato &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; per proteggere con login il backend dell'applicazione, ma esiste di fatto un solo utente, il super-amministratore, creato automaticamente con il caricamento dei dati di esempio (fixtures). Usando le funzioni di sfDoctrineGuardPlugin possono già essere aggiunti nuovi utenti e gruppi, ma non venendo gestiti permessi specifici, ogni nuovo utente può operare senza alcuna restrizione, risultando a tutti gli effetti pratici indistinguibile dal super-amministratore.&lt;/p&gt;

&lt;p&gt;Inoltre per ogni utente possono essere salvate solo le informazioni di base gestite dalla classe &lt;strong&gt;sfGuardUser&lt;/strong&gt;: nome utente e password. Questo è il primo aspetto da migliorare con la creazione di un &lt;strong&gt;profilo utente&lt;/strong&gt; che ci consenta di gestire informazioni aggiuntive: nome, cognome ed indirizzo e-mail; almeno per il momento, altri campi potranno essere aggiunti in seguito se necessario.&lt;/p&gt;

&lt;p&gt;Ho seguito &lt;a href="http://www.symfony-project.org/blog/2008/11/12/call-the-expert-customizing-sfdoctrineguardplugin"&gt;questa guida&lt;/a&gt; pubblicata sul blog ufficiale di symfony.&lt;/p&gt;

&lt;p&gt;Per prima cosa si crea nello schema una classe per il profilo utente&lt;/p&gt;

&lt;pre&gt;
config/doctrine/schema.yml

LyraUserProfile:
  tableName: users
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
    first_name:
      type: string(80)
    last_name:
      type: string(80)
    email:
      type: string(150)
  relations:
    User:
      class: sfGuardUser
      local: user_id
      foreign: id
      type: one
      foreignType: one
      foreignAlias: Profile
      onDelete: CASCADE
&lt;/pre&gt;
&lt;p&gt;L'impostazione 'onDelete: CASCADE' non è presente nella guida: credo sia una svista perché altrimenti l'esistenza della relazione rende impossibile cancellare un utente dal backend.&lt;/p&gt;

&lt;p&gt;Si modificano le fixtures per includere le informazioni del profilo negli utenti creati con i dati di esempio, nel mio caso il super-amministratore&lt;/p&gt;

&lt;pre&gt;
data/fixtures/users.yml

sfGuardUser:
  admin:
    username:       admin
    password:       admin
    is_super_admin: true

#User profile

LyraUserProfile:
  p_admin:
    first_name: Lyra
    last_name: Administrator
    email: lyra@localhost
    User: admin
&lt;/pre&gt;

&lt;p&gt;Si ricostruiscono le classi del modello e si ricaricano i dati di esempio con&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build --all --and-load&lt;/pre&gt;

&lt;p&gt;La sintassi del comando è quella da usare in symfony 1.4.&lt;/p&gt;

&lt;p&gt;A questo punto si deve personalizzare il form per la creazione e modifica di un utente nel backend in modo da poter inserire le informazioni del profilo. La guida citata all'inizio dice di copiare in &lt;em&gt;lib/form/doctrine&lt;/em&gt; il file &lt;em&gt;sfGuardUserAdminForm.class.php&lt;/em&gt; che si trova nella cartella &lt;em&gt;plugins/sfDoctrineGuardPlugin/lib/form/doctrine&lt;/em&gt; e procedere con la personalizzazione della copia. In questo modo un successivo aggiornamento del plugin non sovrascriverà le nostre modifiche. Il problema è che facendo in questo modo a me la cosa non funziona, in particolare si verificano errori nei test funzionali. Infatti durante i test l'autoload carica la classe &lt;strong&gt;sfGuardUserAdminForm&lt;/strong&gt; originale (quella nella cartella del  plugin) e non la copia modificata nella cartella &lt;em&gt;lib/form/docrine&lt;/em&gt; del progetto.&lt;/p&gt;

&lt;p&gt;Non sono stato tanto ad indagare ed ho preso una strada leggermente diversa: ho creato una classe &lt;strong&gt;LyraUserAdminForm&lt;/strong&gt; derivata da &lt;strong&gt;sfGuardUserAdminForm&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;
lib/form/doctrine/LyraUserAdminForm.class.php

class LyraUserAdminForm extends sfGuardUserAdminForm
{
  public function configure()
  {
    parent::configure();

    $profileForm = new LyraUserProfileForm($this-&amp;gt;object-&amp;gt;Profile);
    unset($profileForm['id'], $profileForm['user_id']);
    $this-&amp;gt;embedForm('user_profile', $profileForm);
    $this-&amp;gt;widgetSchema['user_profile']-&amp;gt;setLabel(false);
  }
}
&lt;/pre&gt;

&lt;p&gt;Il form con le informazioni del profilo viene inizializzato con l'oggetto ottenuto dalla relazione definita nello schema (&lt;strong&gt;Profile&lt;/strong&gt;, foreignAlias della relazione &lt;strong&gt;User&lt;/strong&gt; di &lt;strong&gt;LyraUserProfile&lt;/strong&gt;) e poi incluso nel form principale con &lt;strong&gt;embedForm()&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Bisogna poi modificare &lt;em&gt;generator.yml&lt;/em&gt; perché venga utilizzata la nostra classe al posto di &lt;strong&gt;sfGuardUserAdminForm&lt;/strong&gt; e per visualizzare in un 'pannello' il form embedded (user_profile) con le informazioni del profilo.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/sfGuardUser/config/generator.yml
...
     form:
        class: &lt;span style="color:blue"&gt;LyraUserAdminForm&lt;/span&gt;
        display:
          "NONE": [username, password, password_again]
          &lt;span style="color:blue"&gt;PANEL_PROFILE: [user_profile]&lt;/span&gt;
          PANEL_PERMISSIONS: [is_active, is_super_admin, groups_list, permissions_list]
...
&lt;/pre&gt;
&lt;p&gt;Dopo aver modificato questo file ho dovuto anche rimuovere (in realtà ne ho commentato il contenuto) il file &lt;em&gt;generator.yml&lt;/em&gt; originale installato dal plugin in &lt;em&gt;plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config/&lt;/em&gt;. Diversamente è impossibile personalizzare alcune opzioni di configurazione. Mi sembra di ricordare qualche post nel forum di symfony dove veniva fatto notare questo problema. La cosa seccante è la necessità di ripetere l'operazione ad ogni aggiornamento del plugin, ma soluzioni alternative non ne ho trovate.&lt;/p&gt;

&lt;p&gt;Alla fine ho creato i file di lingua per la traduzione dell'interfaccia. Tutto il lavoro fatto è visibile nel log delle modifiche della revisione 52.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5036626081677682825?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/G-sb_pFojipvfTnn8A7c8QpblCo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/G-sb_pFojipvfTnn8A7c8QpblCo/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/G-sb_pFojipvfTnn8A7c8QpblCo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/G-sb_pFojipvfTnn8A7c8QpblCo/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/U45YeGgV77g" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5036626081677682825/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/04/symfony-profilo-utente-con-sfguard.html#comment-form" title="8 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5036626081677682825?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5036626081677682825?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/04/symfony-profilo-utente-con-sfguard.html" title="symfony, gestire un profilo utente con sfGuard" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>8</thr:total></entry><entry gd:etag="W/&quot;AkYAQXsyfCp7ImA9WxFREkU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8533907738818557840</id><published>2010-04-26T15:19:00.002+02:00</published><updated>2010-04-26T15:22:20.594+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-26T15:22:20.594+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, ultimi sviluppi</title><content type="html">&lt;p&gt;Lo sviluppo di Lyra continua anche se ultimamente non ho ho avuto molto tempo di scrivere sul blog. Faccio quindi un breve punto della situazione riassumendo le modifiche apportate dalle ultime revisioni. Il codice come al solito può essere consultato nel &lt;a href="http://code.google.com/p/lyra-cms/"&gt;repository&lt;/a&gt; su Google Code.&lt;/p&gt;

&lt;h2&gt;Modifiche alla gestione metatags dei contenuti&lt;/h2&gt;

&lt;p&gt;La gestione dei &lt;strong&gt;metatags&lt;/strong&gt; (meta-title, meta-description, meta-keywords, meta-robots) dei contenuti avviene tramite un modulo apposito (classe &lt;strong&gt;LyraMetatagsForm&lt;/strong&gt;) incorporato nel modulo di inserimento / modifica del contenuto. Sono stati inoltre creati due parametri di configurazione globale per aggiungere un prefisso o un suffisso al meta-titolo della pagina. Questo permette ad esempio di avere titoli delle pagine del tipo&lt;/p&gt;

&lt;pre&gt;Titolo articolo | Nome sito&lt;/pre&gt;

&lt;p&gt;oppure&lt;/p&gt;

&lt;pre&gt;Nome sito | Titolo articolo&lt;/pre&gt;

&lt;p&gt;Dove 'Nome sito |',  '| Nome sito' sono il valore di un parametro di configurazione e possono essere quindi impostati a piacere. (revisione 43).&lt;/p&gt;

&lt;h2&gt;Modifiche al layout del backend&lt;/h2&gt;

&lt;p&gt;Ho fatto qualche modifica per migliorare l'aspetto e rendere più pulita e ordinata l'area di amministrazione (revisioni 44 e 47). 
&lt;/p&gt;
&lt;h2&gt;Ottimizzazione liste di selezione etichette&lt;/h2&gt;

&lt;p&gt;La generazione delle liste di selezione delle etichette da assegnare ad un contenuto è stata ottimizzata e richiede adesso una sola query anche quando ci sono più cataloghi utilizzabili per il tipo di contenuto (revisione 45). Inoltre la gestione delle liste di selezione delle etichette è stata spostata dalla classe &lt;strong&gt;LyraArticleForm&lt;/strong&gt; a &lt;strong&gt;LyraContentForm&lt;/strong&gt;, in quanto questa parte dovrà essere utilizzata anche per i tipi di contenuto diversi da 'Article',  che ancora non esistono (revisone 46).&lt;/p&gt;

&lt;h2&gt;Modifica alla gestione dei parametri di configurazione&lt;/h2&gt;

&lt;p&gt;La gestione dei parametri di configurazione globali e dei tipi di contenuto è stata ristrutturata. Il valore di un parametro di configurazione non viene più letto con il metodo &lt;strong&gt;getCfg()&lt;/strong&gt; di &lt;strong&gt;LyraContent&lt;/strong&gt;, ma viene utilizzata una classe apposita (&lt;strong&gt;LyraConfig&lt;/strong&gt;) istanziata nelle azioni e passata al template. Mi sembra questa una soluzione più pulita e più aderente al pattern MVC dell'implementazione precedente (revisione 48).&lt;/p&gt;

&lt;h2&gt;Aggiornamento del framework&lt;/h2&gt;

&lt;p&gt;Lyra utilizza adesso l'ultima versione di symfony (1.4.4). Anche il plugin &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; è stato aggiornato. (revisioni 49, 50, 51).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8533907738818557840?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/bp7Wmjd9ZemF7IqiwWZnBpHhu80/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bp7Wmjd9ZemF7IqiwWZnBpHhu80/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/bp7Wmjd9ZemF7IqiwWZnBpHhu80/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/bp7Wmjd9ZemF7IqiwWZnBpHhu80/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/m71qLGW9ja0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8533907738818557840/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/04/lyra-ultimi-sviluppi.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8533907738818557840?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8533907738818557840?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/04/lyra-ultimi-sviluppi.html" title="Lyra, ultimi sviluppi" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CUYMQXY6cCp7ImA9WxBWF00.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-2844044318102737812</id><published>2010-02-09T09:13:00.003+01:00</published><updated>2010-02-09T09:13:00.818+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-09T09:13:00.818+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, tipo di contenuto 'pagina'</title><content type="html">&lt;p&gt;Chi ha familiarità con Wordpress o Drupal conosce la differenza tra &lt;strong&gt;articolo&lt;/strong&gt; (&lt;strong&gt;story&lt;/strong&gt; in Drupal) e &lt;strong&gt;pagina&lt;/strong&gt;. Un concetto grosso modo analogo è presente in Joomla che distingue i contenuti standard dai &lt;strong&gt;contenuti statici&lt;/strong&gt; (Joomla 1.0) o dai contenuti &lt;strong&gt;non categorizzati&lt;/strong&gt; (Joomla 1.5). Chi non sapesse invece di cosa si sta parlando troverà un'ottima spiegazione in questo &lt;a href="http://cms.html.it/guide/lezione/3540/pagine-wordpress/"&gt;articolo&lt;/a&gt; di Html.it.&lt;/p&gt;

&lt;p&gt;La ragione di questa distinzione è semplice: in molti casi (in un blog, ma anche nei siti che pubblicano notizie come quotidiani online e simili) è utile gestire in modo diverso gli articoli veri e propri che contengono informazioni 'datate' e i contenuti che contengono informazioni meno sensibili al decorrere del tempo e quindi in un certo senso più 'statiche': ad esempio una pagina con il profilo dell'autore del blog, la descrizione o l'organico dell'azienda.&lt;/p&gt;

&lt;p&gt;Anche il modo in cui sono presentate queste due categorie di contenuti è spesso diverso: di regola un articolo contiene l'indicazione della data di pubblicazione e dell'autore, mentre queste informazioni non sono mostrate in una pagina; gli articoli sono presentati in ordine cronologico in prima pagina o nella pagina della rispettiva categoria, mentre le pagine sono accessibili da link o menù; anche il formato delle URL può essere diverso.&lt;/p&gt;

&lt;p&gt;E se volessimo creare qualcosa del genere in Lyra? Arrivati a questo punto non è difficile farlo: basterà creare un nuovo tipo di contenuto, &lt;strong&gt;page&lt;/strong&gt;, tanto per non stare ad inventarci nomi diversi, anch'esso gestito dallo stesso modello e modulo che già utilizziamo per il contenuto &lt;strong&gt;article&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In effetti le differenze tra articoli e pagine non sono tanto rilevanti da giustificare la creazione di modelli separati: ci basta 'clonare' un tipo di contenuto esistente e sfruttare i parametri di configurazione (vedere &lt;a href="http://sviluppare-in-rete.blogspot.com/2010/01/lyra-parametri-di-configurazione.html"&gt;Lyra, parametri di configurazione articoli&lt;/a&gt;) per differenziare l'apetto degli articoli e delle pagine.&lt;/p&gt;

&lt;p&gt;Per prima cosa ho inserito un nuovo record nella tabella &lt;em&gt;content_types&lt;/em&gt; nel file con i dati di esempio.&lt;/p&gt;
&lt;pre&gt;
data/fixtures/content_types.yml

LyraContentType:
  ...
  Pagina:
    name: Page
    type: page
    module: article
    model: LyraArticle
    ...
&lt;/pre&gt;
&lt;p&gt;Ho modificato il file &lt;em&gt;routing.yml&lt;/em&gt; del backend perché a questo punto fa comodo avere l'ID del tipo di contenuto nella rotta&lt;/p&gt;
&lt;pre&gt;
apps/backend/config/routing.yml
...
lyra_article:
  class: sfDoctrineRouteCollection
  options:
    model:                LyraArticle
    module:               article
    prefix_path:          /article/:ctype_id
    column:               id
    object_actions:
      publish: get
      unpublish: get
      feature: get
      unfeature: get
    with_wildcard_routes: true
...
&lt;/pre&gt;
&lt;p&gt;e pure quello del frontend per differenziare il formato delle URL di articoli e pagine&lt;/p&gt;
&lt;pre&gt;
apps/frontend/config/routing.yml

page_show:
  url: /: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;I parametri del nuovo tipo di contenuto possono essere impostati dal backend, ad esempio possiamo modificare l'aspetto del contenuto &lt;strong&gt;pagina&lt;/strong&gt; in modo da non mostrare autore e data di creazione e non consentire i commenti.&lt;/p&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/S3CCTdKJvNI/AAAAAAAAAJY/-yTe2XCsGKw/s1600-h/lyra-parametri-contenuto.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 210px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/S3CCTdKJvNI/AAAAAAAAAJY/-yTe2XCsGKw/s320/lyra-parametri-contenuto.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5435988020992523474" /&gt;&lt;/a&gt;

&lt;p&gt;Altre modifiche si possono vedere nel log della revisione 41. Il tutto richiede un po' di lavoro manuale, ma è possibile che in futuro queste operazioni possano essere gestite direttamente dal backend.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-2844044318102737812?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/iNnQbfb2YNPcl7OJTdl2xaXSl5o/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/iNnQbfb2YNPcl7OJTdl2xaXSl5o/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/iNnQbfb2YNPcl7OJTdl2xaXSl5o/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/iNnQbfb2YNPcl7OJTdl2xaXSl5o/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/zik0Vlm5ZVQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/2844044318102737812/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/02/lyra-tipo-di-contenuto-pagina.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2844044318102737812?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2844044318102737812?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/02/lyra-tipo-di-contenuto-pagina.html" title="Lyra, tipo di contenuto 'pagina'" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_unGpwtr1q-o/S3CCTdKJvNI/AAAAAAAAAJY/-yTe2XCsGKw/s72-c/lyra-parametri-contenuto.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DkAGQXk6eyp7ImA9WxBWEUU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8231827454067221117</id><published>2010-02-03T09:12:00.001+01:00</published><updated>2010-02-03T09:12:00.713+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-03T09:12:00.713+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, parametri di configurazione globali</title><content type="html">&lt;p&gt;Dopo la gestione dei parametri di configurazione a livello di contenuto e di tipo di contenuto, mi è sembrato logico creare una funzione che consenta di impostare dal backend dei parametri di &lt;strong&gt;configurazione globali&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le definizioni di tali parametri, analogamente a quanto avviene per i parametri dei contenuti, è contenuta in un file YAML (&lt;em&gt;config/lyra_params.yml&lt;/em&gt;). A partire da questo file viene generato il form (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraSettingsForm.class.php?r=38"&gt;LyraSettingsForm&lt;/a&gt;) per l'inserimento e la modifica dei valori che vengono salvati nel database (tabella &lt;em&gt;settings&lt;/em&gt;, modello &lt;strong&gt;LyraSettings&lt;/strong&gt;). Infine la classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/LyraConfig.php?r=38"&gt;LyraCfg&lt;/a&gt; serve a leggere il valore di un parametro.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/S2izM-_h8sI/AAAAAAAAAJQ/mrZ5aN9yG9A/s1600-h/lyra-global-cfg.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 205px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/S2izM-_h8sI/AAAAAAAAAJQ/mrZ5aN9yG9A/s320/lyra-global-cfg.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5433789986071769794" /&gt;&lt;/a&gt;
&lt;p&gt;Come si vede dalla figura attualmente ci sono solo due parametri di configurazione, quando ne serviranno altri sarà sufficiente aggiungere le definizioni in &lt;em&gt;lyra_params.yml&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Tanto per fare un esempio vediamo come si utilizza il parametro &lt;em&gt;Moderazione commenti&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;
config/lyra_params.yml

...
moderate_comments:
    type: list
    choices: [moderate_all, moderate_no_auth, moderate_none]
    default: moderate_all
...
&lt;/pre&gt;
&lt;p&gt;Il tipo &lt;em&gt;list&lt;/em&gt; indica che il parametro può assumere un valore tra quelli indicati in &lt;em&gt;choices&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;strong&gt;moderate_all&lt;/strong&gt;: tutti i commenti sono sempre moderati. Compaiono sul sito solo previa approvazione dell'amministratore.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;moderate_no_auth&lt;/strong&gt;: solo i commenti degli utenti anonimi (cioè che visitano il sito senza aver fatto il login) sono moderati.&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;moderate_none&lt;/strong&gt;: nessuna moderazione, ogni commento è immediatamente visibile sul sito.
&lt;/ul&gt;&lt;/li&gt;
&lt;p&gt;Da notare che gli identificativi dei parametri come i valori delle opzioni sono sempre in inglese nel file YAML, ma vengono tradotti nei file di lingua (nella figura all'inizio si vedono infatti in italiano).&lt;/p&gt;

&lt;p&gt;In questa parte della funzione &lt;strong&gt;updateObject()&lt;/strong&gt; di &lt;strong&gt;LyraCommentForm&lt;/strong&gt;, a seconda del valore scelto per il parametro si imposta o meno a &lt;em&gt;true&lt;/em&gt; il campo &lt;strong&gt;is_active&lt;/strong&gt; (pubblicato) del commento appena inserito.&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/LyraCommentsForm.class.php
...
      switch(&lt;span style="color: blue"&gt;LyraCfg::get('moderate_comments')&lt;/span&gt;) {
        case 'moderate_none':
          $publish = true;
          break;
        case 'moderate_no_auth':
          $publish = $user_auth;
          break;
        case 'moderate_all':
        default:
          $publish = false;
          break;
      }
      $item-&amp;gt;setIsActive($publish);
...
&lt;/pre&gt;
&lt;p&gt;Accenno solo brevemente alle altre modifiche contenute nelle revisioni 37 e 38: i file contenenti le definizioni dei parametri di configurazione dei contenuti / tipi di contenuto non sono più nella cartella &lt;em&gt;config&lt;/em&gt; dell'applicazione, ma nella cartella &lt;em&gt;config&lt;/em&gt; del modulo (backend). Ad esempio &lt;/p&gt;
&lt;pre&gt;config/params/article.yml&lt;/pre&gt;
&lt;p&gt;non esiste più, l'equivalente è ora&lt;/p&gt;
&lt;pre&gt;apps/backend/modules/article/config/params.yml&lt;/pre&gt;

&lt;p&gt;Il codice nel metodo &lt;strong&gt;configure()&lt;/strong&gt; di &lt;strong&gt;LyraArticleForm&lt;/strong&gt; che gestiva la configurazione dei &lt;strong&gt;form embedded&lt;/strong&gt; usati per visualizzare e modificare i parametri di configurazione dell'articolo è stato spostato in &lt;strong&gt;LyraContentForm&lt;/strong&gt;. Questa parte sarà standard e comune a tutti i tipi di contenuto, inserendola nella classe base eviteremo future duplicazioni di codice. Il tutto sarà più chiaro in seguito appena saranno creati altri tipi di contenuto in aggiunta ad &lt;em&gt;article&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sono stati aggiunti nuovi campi a &lt;strong&gt;LyraContentType&lt;/strong&gt;: &lt;em&gt;type&lt;/em&gt;, &lt;em&gt;model&lt;/em&gt;, &lt;em&gt;plugin&lt;/em&gt;. A cosa servono si avrà modo di vederlo nelle prossime puntate. Per il momento è tutto.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8231827454067221117?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/9adWhB_sT7_TQT9TDJoohpLr44s/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/9adWhB_sT7_TQT9TDJoohpLr44s/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/9adWhB_sT7_TQT9TDJoohpLr44s/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/9adWhB_sT7_TQT9TDJoohpLr44s/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/tB9uSLzWKro" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8231827454067221117/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/02/lyra-parametri-di-configurazione.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8231827454067221117?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8231827454067221117?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/02/lyra-parametri-di-configurazione.html" title="Lyra, parametri di configurazione globali" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/S2izM-_h8sI/AAAAAAAAAJQ/mrZ5aN9yG9A/s72-c/lyra-global-cfg.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUcMSH05eip7ImA9WxBQFEo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-2564695748880841042</id><published>2010-01-14T14:50:00.000+01:00</published><updated>2010-01-14T14:51:29.322+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-01-14T14:51:29.322+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, parametri di configurazione articoli</title><content type="html">&lt;p&gt;Fino a questo punto l'aspetto dei contenuti è determinato esclusivamente dal &lt;strong&gt;layout&lt;/strong&gt; e dai &lt;strong&gt;template&lt;/strong&gt; creati nelle diverse &lt;strong&gt;action&lt;/strong&gt;. Si presenta quindi l'esigenza di consentire all'utente un certo grado di personalizzazione dei contenuti attraverso opzioni o parametri di configurazione accessibili dal backend dell'applicazione.&lt;/p&gt;

&lt;p&gt;Questo tipo di funzionalità è presente in molti cms e se ne capisce facilmente la ragione. Solo per fare un esempio, costringere l'utente alla modifica dei template per impostare un diverso formato della data di visualizzazione degli articoli sarebbe scomodo e quasi sicuramente causa di errori.&lt;/p&gt;

&lt;p&gt;Vediamo quindi come verranno gestiti i parametri di configurazione dei contenuti in Lyra. Ogni parametro potrà essere impostato a livello di singolo articolo e di tipo di contenuto: se il valore di un parametro non è impostato in modo specifico per un singolo articolo, sarà utilizzato il valore del parametro impostato per il tipo di contenuto.&lt;/p&gt;

&lt;p&gt;Facciamo un esempio.&lt;/p&gt;

&lt;p&gt;1) parametri del tipo di contenuto &lt;em&gt;article&lt;/em&gt;&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/S08gZtzSNaI/AAAAAAAAAJA/oWbieNVdgdM/s1600-h/lyra-params1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 223px; height: 259px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/S08gZtzSNaI/AAAAAAAAAJA/oWbieNVdgdM/s320/lyra-params1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5426591702168253858" /&gt;&lt;/a&gt;
&lt;p&gt;2) parametri di un singolo articolo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/S08giu4dD0I/AAAAAAAAAJI/2XLkY9XoWJE/s1600-h/lyra-params2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 241px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/S08giu4dD0I/AAAAAAAAAJI/2XLkY9XoWJE/s320/lyra-params2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5426591857077194562" /&gt;&lt;/a&gt;
&lt;p&gt;Con riguardo all'articolo in questione, il parametro 'Mostra data' è impostato su &lt;strong&gt;No&lt;/strong&gt;, tutti gli altri prendono i valori predefiniti impostati sul tipo di contenuto (nell'esempio saranno tutti &lt;strong&gt;Sì&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Per vedere come i valori dei parametri di configurazione vengono letti dall'applicazione basta dare uno sguardo ad uno dei template del modulo &lt;strong&gt;article&lt;/strong&gt;, ad esempio questo&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/templates/_byline.php

&amp;lt;div class=&amp;quot;article-byline&amp;quot;&amp;gt;
  &amp;lt;?php if(&lt;span style="color:blue"&gt;$item-&amp;gt;getCfg('show_author')&lt;/span&gt;): ?&amp;gt;
  &amp;lt;span class=&amp;quot;article-author&amp;quot;&amp;gt;
    &amp;lt;?php echo __('ARTICLE_WRITTEN_BY', array('%author%'=&amp;gt; $item-&amp;gt;getArticleCreatedBy()-&amp;gt;getUsername()));?&amp;gt;
  &amp;lt;/span&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;
  &amp;lt;?php if(&lt;span style="color:blue"&gt;$item-&amp;gt;getCfg('show_date')&lt;/span&gt;): ?&amp;gt;
  &amp;lt;span class=&amp;quot;article-date&amp;quot;&amp;gt;
    &amp;lt;?php echo __('ARTICLE_WRITTEN_ON', array('%date%'=&amp;gt;format_date($item-&amp;gt;getCreatedAt(),'dd MMMM, yyyy'))); ?&amp;gt;
  &amp;lt;/span&amp;gt;
  &amp;lt;?php endif; ?&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Il metodo &lt;strong&gt;getCfg()&lt;/strong&gt; è implementato in una nuova classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/model/doctrine/LyraContent.class.php?r=35"&gt;LyraContent&lt;/a&gt;. Si sfrutta l'ereditarietà di &lt;strong&gt;Doctrine&lt;/strong&gt; per derivare &lt;strong&gt;LyraArticle&lt;/strong&gt; da questa classe base&lt;/p&gt;

&lt;pre&gt;
config/doctrine/schema.yml

LyraContent:
  columns:
    ctype_id:
      type: integer(4)
    params:
      type: clob

LyraArticle:
  tableName: articles
  inheritance:
    extends: LyraContent
    type: concrete
...
&lt;/pre&gt;
&lt;p&gt;Anche le classi che gestiranno gli altri tipi di contenuto saranno derivate da &lt;strong&gt;LyraContent&lt;/strong&gt; in modo che le funzionalità relative ai parametri di configurazione siano comuni a tutti i contenuti gestiti dal cms.&lt;/p&gt;

&lt;p&gt;L'impostazione dei parametri avviene nel backend tramite i form da cui provengono le schermate riportate all'inizio di questo articolo. La classe relativa è &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraParamsForm.class.php?r=35"&gt;LyraParamsForm&lt;/a&gt;: i &lt;strong&gt;widget&lt;/strong&gt; per inserire il valore di ogni parametro sono generati dinamicamente a partire dalle informazioni contenuto in un file YAML. Per quanto riguarda il tipo di contenuto &lt;em&gt;article&lt;/em&gt; abbiamo &lt;em&gt;config/params/article.yml&lt;/em&gt;, file analoghi saranno creati per ciascun tipo di contenuto. Per creare un nuovo parametro basta aggiungerne la definizione in questo file.&lt;/p&gt;

&lt;p&gt;Analogamente a quanto visto per il form con le liste di selezione delle etichette, anche il form con i parametri di configurazione non viene utilizzato da solo, ma incorporato (&lt;strong&gt;embedded&lt;/strong&gt;) in altri form: vedere le istruzioni nei metodi &lt;strong&gt;configure()&lt;/strong&gt; di &lt;strong&gt;BackendLyraArticleForm&lt;/strong&gt; e &lt;strong&gt;LyraContentTypeForm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nomino infine la classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/LyraParams.class.php?r=35"&gt;LyraParams&lt;/a&gt; che contiene funzioni per la lettura/scrittura dei parametri di configurazione da e nel  database (campo &lt;em&gt;params&lt;/em&gt; di &lt;strong&gt;LyraContent&lt;/strong&gt; e &lt;strong&gt;LyraContentType&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Per il momento sono disponibili un numero limitato di opzioni di configurazione, altre ne saranno aggiunte via via che servono, ho scritto anche i test funzionali per testare questa parte. Tutto questo è disponibile nella &lt;strong&gt;revisione 35&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-2564695748880841042?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/oCED2KhYh3DRz4BEhVhHo2O85GM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/oCED2KhYh3DRz4BEhVhHo2O85GM/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/oCED2KhYh3DRz4BEhVhHo2O85GM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/oCED2KhYh3DRz4BEhVhHo2O85GM/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/khELUNIb1es" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/2564695748880841042/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/01/lyra-parametri-di-configurazione.html#comment-form" title="2 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2564695748880841042?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/2564695748880841042?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2010/01/lyra-parametri-di-configurazione.html" title="Lyra, parametri di configurazione articoli" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_unGpwtr1q-o/S08gZtzSNaI/AAAAAAAAAJA/oWbieNVdgdM/s72-c/lyra-params1.png" height="72" width="72" /><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CEAESXk6fCp7ImA9WxBREUs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-978780039034225101</id><published>2009-12-30T09:43:00.001+01:00</published><updated>2009-12-30T09:45:08.714+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-30T09:45:08.714+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, personalizzare il form articoli</title><content type="html">&lt;p&gt;Ho lavorato un po' sul form per l'inserimento e modifica degli articoli in Lyra. Infatti fino ad ora la struttura del form è stata quella generata automaticamente da symfony, a parte piccole modifiche. Questo è invece il risultato del lavoro svolto (cliccare l'immagine per ingrandirla).&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s1600-h/form-articolo.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 311px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s320/form-articolo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5420947125712561426" /&gt;&lt;/a&gt;
&lt;p&gt;I campi sono organizzati in blocchi o 'pannelli' e il form si sviluppa su due colonne per sfruttare lo spazio orizzontale. Ecco a grandi linee i passi seguiti per personalizzare l'aspetto del form in questo modo.&lt;/p&gt;

&lt;p&gt;Per prima cosa ho creato una classe &lt;strong&gt;form formatter&lt;/strong&gt; come già fatto per il form commenti (vedi &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/10/personalizzare-form-in-symfony.html"&gt;Personalizzare l'aspetto dei form in symfony&lt;/a&gt; ). &lt;/p&gt;

&lt;p&gt;La classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/sfWidgetFormSchemaFormatterLyraContent.php?r=32"&gt;sfWidgetFormSchemaFormatterLyraContent&lt;/a&gt; è molto semplice: la proprietà &lt;em&gt;rowFormat&lt;/em&gt; serve a stabilire l'ordine degli elementi (etichetta, campo, messaggio di aiuto e messaggio di errore di validazione) all'interno di ogni riga del form; inoltre è stato ridefinito il metodo &lt;strong&gt;generateLabel()&lt;/strong&gt; in modo da aggiungere un attributo di classe particolare (&lt;em&gt;field-bool&lt;/em&gt;) a tutti i tag &amp;lt;label&amp;gt; relativi a campi boolean, questo per poter allineare, tramite css, l'etichetta di questi campi in modo diverso rispetto a quelle degli altri campi.&lt;/p&gt;

&lt;p&gt;Ci si potrebbe chiedere la ragione di un &lt;strong&gt;form formatter&lt;/strong&gt; per determinare l'apetto di un singolo form. Adesso il form è uno solo, quando saranno gestiti altri tipi di contenuto ognuno avrà il suo form di inserimento e modifica e tutti dovranno avere un aspetto consistente: la classe formatter ci faciliterà questo compito.&lt;/p&gt;

&lt;p&gt;Si sono rese necessarie diverse modifiche alla classe &lt;strong&gt;LyraArticleForm&lt;/strong&gt;. Invece di postare tutto il codice qui è preferibile un link al repository che mostri le differenze tra le &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/lib/form/doctrine/LyraArticleForm.class.php"&gt;revisioni 31 e 32 della classe.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La proprietà &lt;em&gt;panels&lt;/em&gt; determina l'ordine dei pannelli e i campi contenuti in ciascun pannello, la proprietà &lt;em&gt;break_at&lt;/em&gt; determina l'inizio della seconda colonna: entrambe vengono impostate nel metodo &lt;strong&gt;configure()&lt;/strong&gt; e utilizzate nel template (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/frontend/modules/article/templates/_form.php?r=32"&gt;_form.php&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Il pannello Etichette viene gestito in modo particolare. Visto che è l'utente che sceglie quali e quanti cataloghi possono essere utilizzati per categorizzare ogni tipo di contenuto, come si è visto a suo tempo, le liste di selezione delle etichette devono essere generate dinamicamente. Ho rimosso questa parte di codice dalla classe &lt;strong&gt;LyraArticleForm&lt;/strong&gt; e creato un form dedicato (classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/lib/form/doctrine/LyraLabelListsForm.class.php?r=32"&gt;LyraLabelListsForm&lt;/a&gt;) che viene incluso nel form principale da queste istruzioni&lt;/p&gt;

&lt;pre&gt;
lib/form/doctrine/LyraArticleForm.class.php

class LyraArticleForm extends BaseLyraArticleForm
{
...
  public function configure()
  {
...
    $label_lists_form = new LyraLabelListsForm(array(), array('ctype_id' =&amp;gt; $ctype_id, 'selected' =&amp;gt; $selected));
    $this-&amp;gt;embedForm('labels', $label_lists_form);
...
  }
}
&lt;/pre&gt;
&lt;p&gt;Demandare la creazione delle liste di selezione delle etichette ad una classe separata ci permetterà di evitare duplicazioni di codice quando sarà necessario gestire altri tipi di contenuto in aggiunta agli articoli. Il metodo &lt;strong&gt;saveLabels()&lt;/strong&gt; è stato modificato per tenere conto di questa modifica.&lt;/p&gt;

&lt;p&gt;Non è difficile ottenere lo stesso aspetto del form anche nel backend. L'admin generator di symfony è predisposto per suddividere i campi dei form in &lt;em&gt;fieldset&lt;/em&gt; impostati nel file &lt;em&gt;generator.yml&lt;/em&gt;. Per vedere come basta confrontare le differenze del file nelle &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/apps/backend/modules/article/config/generator.yml"&gt;revisioni 31 e 32&lt;/a&gt;. Non resta che modificare il template per utilizzare i fieldset come pannelli disposti su due colonne: &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/generator/sfDoctrineModule/admin/template/templates/_form.php?r=32"&gt;_form.php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Oltre a tutto questo ho aggiunto un campo &lt;strong&gt;ctype_id&lt;/strong&gt; alla tabella articoli (modello &lt;strong&gt;LyraArticle&lt;/strong&gt;) che viene utilizzato in &lt;a href="http://code.google.com/p/lyra-cms/source/diff?spec=svn32&amp;r=32&amp;format=side&amp;path=/trunk/config/doctrine/schema.yml"&gt;schema.yml&lt;/a&gt; come chiave di una relazione &lt;strong&gt;ContentType&lt;/strong&gt; che lega articoli e tipi di contenuto. Mi rendo conto che al momento non se ne capisce molto l'utilità in quanto tutti i record della tabella articoli hanno lo stesso tipo di contenuto ed i tipi di contenuto che saranno creati in seguito avranno proprie tabelle. Vedremo però che ci sono casi in cui tornerà comodo avere l'ID del tipo di contenuto sui singoli record.&lt;/p&gt;

&lt;p&gt;Per il momento l'immediata conseguenza di questa modifica è la necessità per chi si allinea alla &lt;strong&gt;revisione 32&lt;/strong&gt; di eseguire dopo il checkout i comandi&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build --all --and-load
./symfony cc
&lt;/pre&gt;

&lt;p&gt;La sintassi del comando &lt;strong&gt;doctrine:build&lt;/strong&gt; è quella propria di symfony 1.3 e 1.4, come già detto la vecchia sintassi (&lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt;) è ancora valida in symfony 1.3, ma non lo sarà più dalla versione 1.4 per cui è bene abbandonarla subito.&lt;/p&gt;
 
&lt;p&gt;La spiegazione delle modifiche è stata sintetica, chi abbia bisogno di qualsiasi chiarimento può lasciare un commento (finisco anche in rima). Al prossimo anno.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-978780039034225101?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/S-Z8sgRwC9ELTwdei-o-o2d_tMo/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/SAqjlxoHLOQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/978780039034225101/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/lyra-personalizzare-il-form-articoli.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/978780039034225101?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/978780039034225101?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/lyra-personalizzare-il-form-articoli.html" title="Lyra, personalizzare il form articoli" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/SzsSsH5FpRI/AAAAAAAAAI4/quvJ_8ZJgHc/s72-c/form-articolo.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D0QMQXo9cCp7ImA9WxBSFUg.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-1916208773667093528</id><published>2009-12-23T09:03:00.001+01:00</published><updated>2009-12-23T09:03:00.468+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-23T09:03:00.468+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Aggiornato Lyra a symfony 1.3</title><content type="html">&lt;p&gt;Ho aggiornato &lt;strong&gt;Lyra&lt;/strong&gt; a &lt;strong&gt;symfony 1.3.1&lt;/strong&gt;. Per farlo ho cancellato la cartella &lt;em&gt;symfony&lt;/em&gt; dal repository Google Code ed impostato la proprietà &lt;strong&gt;svn:externals&lt;/strong&gt; su &lt;em&gt;lib/vendor&lt;/em&gt; a questo valore:&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;symfony http://svn.symfony-project.com/tags/RELEASE_1_3_1/&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;In effetti inizialmente avevo importato nel repository l'intera copia locale del progetto incluso il framework. Ma continuando in questo modo sarei stato costretto a fare il commit dell'intero framework ad ogni aggiornamento di symfony, con perdita di tempo e spreco di spazio sul repository. Visto che non è certo mia intenzione fare modifiche al framework è molto meglio utilizzare &lt;strong&gt;svn:externals&lt;/strong&gt; in modo da ricevere il contenuto di &lt;em&gt;lib/vendor/symfony&lt;/em&gt; direttamente dal repository di symfony. Questa è anche la procedura raccomandata dalla documentazione ufficiale.&lt;/p&gt;

&lt;p&gt;Aggiornato il framework, ho seguito le istruzioni riportate sul sito di symfony per il passaggio di un progetto dalla versione 1.2 alla 1.3 ed eseguito dalla cartella principale dell'applicazione il comando&lt;/p&gt;

&lt;pre&gt;&lt;p&gt;./symfony project:upgrade1.3&lt;/p&gt;&lt;/pre&gt;

&lt;p&gt;Ho subito ricevuto un errore! Riporto la causa perché può capitare a chi si trovi nella stessa situazione. Visto che per Lyra utilizzo &lt;strong&gt;Doctrine&lt;/strong&gt;, nella classe &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/config/ProjectConfiguration.class.php?r=23"&gt;ProjectConfiguration&lt;/a&gt; era presente questa istruzione per disabilitare il plugin &lt;strong&gt;Propel&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
config/ProjectConfiguration.class.php
...
class ProjectConfiguration extends sfProjectConfiguration
{
...
$this-&amp;gt;disablePlugins(array('sfPropelPlugin'));
...
}
&lt;/pre&gt;
&lt;p&gt;Dato che in symfony 1.3 Propel non è più l'ORM predefinito, questa riga va rimossa altrimenti la disabilitazione di un plugin non abilitato produce un errore che fa abortire il task &lt;strong&gt;project:upgrade1.3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Una volta risolto questo problema la procedura di aggiornamento è stata portata a termine correttamente. Quindi è bastato ricostruire le classi del modello con&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build --all-classes
./symfony cc
&lt;/pre&gt;

&lt;p&gt;Sono state poi necessarie alcune piccole correzioni dovute alla nuova versione di Doctrine (symfony 1.3 utilizza Doctrine 1.2). Chi fosse interessato ai dettagli può consultare il log SVN relativo alla &lt;strong&gt;revisione 31&lt;/strong&gt;: i file modificati sono molti, ma la maggior parte delle modifiche sono state fatte automaticamente dal task &lt;strong&gt;project:upgrade1.3&lt;/strong&gt;. L'aggiornamento, a parte il primo intoppo iniziale, è stato molto semplice.&lt;/p&gt;

&lt;p&gt;A chi volesse provare la nuova versione, dopo il checkout dal repository consiglierei di ricreare non solo le classi, ma anche di azzerare e generare il database ricaricando i dati di esempio. Almeno è quello che ho fatto io con&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build --all --and-load&lt;/pre&gt;

&lt;p&gt;il comando è equivalente a&lt;/p&gt;

&lt;pre&gt;./symfony doctrine:build-all-reload&lt;/pre&gt;

&lt;p&gt;che per il momento può essere ancora utilizzato anche se è bene iniziare ad usare la nuova sintassi che sarà l'unica accettata da symfony 1.4. L'elenco dei nuovi comandi 'build' di Doctrine si ottiene con&lt;/p&gt;

&lt;pre&gt;./symfony help doctrine:build&lt;/pre&gt;

&lt;p&gt;Alla fine non fa male la pulizia della cache&lt;/p&gt;

&lt;pre&gt;./symfony cc&lt;/pre&gt;

&lt;p&gt;Al più presto sarà fatto l'aggiornamento a symfony 1.4.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-1916208773667093528?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/beBo2BgU-P6OLQHVnsOzp2ZRzhU/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/qF_TBtIamH0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/1916208773667093528/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/aggiornato-lyra-symfony-13.html#comment-form" title="3 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1916208773667093528?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/1916208773667093528?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/aggiornato-lyra-symfony-13.html" title="Aggiornato Lyra a symfony 1.3" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;A0ANQn8yeCp7ImA9WxBSEEU.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3570149999050753197</id><published>2009-12-17T23:36:00.002+01:00</published><updated>2009-12-17T23:43:13.190+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-17T23:43:13.190+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, metodi 'magici' find di Doctrine</title><content type="html">&lt;p&gt;&lt;strong&gt;Doctrine&lt;/strong&gt; offre la possibilità di interrogare una tabella del database attraverso metodi speciali definiti 'magic finders'. Vediamo alcuni esempi che utilizzano il database di &lt;strong&gt;Lyra&lt;/strong&gt;. Ad esempio per interrogare la tabella &lt;em&gt;articles&lt;/em&gt; (classe &lt;strong&gt;LyraArticle&lt;/strong&gt;) per prima cosa si ottiene un'istanza della classe &lt;strong&gt;Doctrine_Table&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;$dt = Doctrine::getTable('LyraArticle');&lt;/pre&gt;

&lt;p&gt;Il caso più semplice è la ricerca di un record attraverso la chiave primaria&lt;/p&gt;

&lt;pre&gt;$article = $dt-&amp;gt;find(1);&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;$article&lt;/strong&gt; contiene il record con chiave primaria (nel nostro caso il campo ID di &lt;em&gt;articles&lt;/em&gt;) uguale a 1. Per effettuare una ricerca su un campo diverso dalla chiave primaria si utilizza la variante &lt;strong&gt;findBy&lt;/strong&gt;&lt;em&gt;nomecampo&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;$articles = $dt-&amp;gt;findByIsActive(true);&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; contiene l'insieme degli articoli pubblicati (campo &lt;strong&gt;is_active&lt;/strong&gt; è &lt;em&gt;true&lt;/em&gt;). Da notare che quando, come in questo caso, il nome del campo è composto da più parole separate dal carattere sottolineato, il nome del metodo si crea utilizzando il &lt;em&gt;camel case&lt;/em&gt; (campo: &lt;strong&gt;is_active&lt;/strong&gt;, metodo: &lt;strong&gt;findByIsActive()&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Si può effettuare una ricerca su più campi, ad esempio per ottenere tutti gli articoli pubblicati (&lt;strong&gt;is_active&lt;/strong&gt; &lt;em&gt;true&lt;/em&gt;) ed impostati per apparire in prima pagina (&lt;strong&gt;is_featured&lt;/strong&gt; &lt;em&gt;true&lt;/em&gt;)&lt;/p&gt;

&lt;pre&gt;$articles = $dt-&amp;gt;findByIsActiveAndIsFeatured(true, true);&lt;/pre&gt;

&lt;p&gt;Oltre ad &lt;strong&gt;AND&lt;/strong&gt; si può utilizzare anche &lt;strong&gt;OR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I metodi &lt;strong&gt;findBy&lt;/strong&gt; restituiscono un insieme di record (&lt;strong&gt;Doctrine_Collection&lt;/strong&gt;), quando si preferisce che venga restituito un singolo oggetto record (&lt;strong&gt;Doctrine_Record&lt;/strong&gt;) si deve utilizzare &lt;strong&gt;findOneBy&lt;/strong&gt;&lt;em&gt;nomecampo&lt;/em&gt;, ad esempio&lt;/p&gt;

&lt;pre&gt;$article = $dt-&amp;gt;findOneByTitle('Titolo articolo');&lt;/pre&gt;

&lt;p&gt;Anche se esistesse più di un articolo con titolo 'Titolo articolo' solo il primo record sarebbe restituito in &lt;strong&gt;$article&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Abbiamo anche la possibilità di ottenere array invece che oggetti come valore di ritorno da ogni metodo 'magic finder'. In base alla scelta cambia naturalmente il modo di accedere ai valori dei campi. Ad esempio&lt;/p&gt;

&lt;pre&gt;
$articles = Doctrine::getTable('LyraArticle')
  -&amp;gt;FindByIsActive(true);
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; è una collezione di oggetti, quindi i valori dei campi si ottengono come proprietà dell'oggetto record.&lt;/p&gt;
&lt;pre&gt;
foreach($articles as $a) {
  echo $a-&gt;title;
}
&lt;/pre&gt;
&lt;p&gt;Alternativamente&lt;/p&gt;
&lt;pre&gt;
$articles = Doctrine::getTable('LyraArticle')
      -&amp;gt;FindByIsActive(true, Doctrine::HYDRATE_ARRAY);
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$articles&lt;/strong&gt; è un array ed i valori dei singoli campi si ottengono tramite indice.&lt;/p&gt;
&lt;pre&gt;
foreach($articles as $a) {
  echo $a['title'];
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3570149999050753197?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/qUbH9D6eIvuBlhKWq1TBIAW-OV0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/85IBB9N1-3o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3570149999050753197/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/symfony-metodi-magici-find-di-doctrine.html#comment-form" title="2 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3570149999050753197?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3570149999050753197?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/symfony-metodi-magici-find-di-doctrine.html" title="symfony, metodi 'magici' find di Doctrine" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;Dk8GRncyfyp7ImA9WxBTFE4.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-354838157107144814</id><published>2009-12-10T09:45:00.001+01:00</published><updated>2009-12-10T09:47:07.997+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-10T09:47:07.997+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Demo Lyra online</title><content type="html">&lt;p&gt;Ho messo online un mini sito dimostrativo di &lt;strong&gt;Lyra&lt;/strong&gt;. Il demo è limitato al frontend, in seguito mi ripropongo di creare un account utente di prova per consentire a chi lo desideri di giocare un po' anche con il backend. Per fare questo devo lavorare ancora sulla gestione dei permessi in quanto l'unico tipo di utente disponibile al momento è il super-amministratore al cui account, per ovvie ragioni, non posso dare accesso pubblico.&lt;/p&gt;

&lt;p&gt;Naturalmente chi ha già familiarità con &lt;strong&gt;symfony&lt;/strong&gt; o ha avuto la pazienza di seguire gli articoli pubblicati fino a questo momento, può fare il checkout dal repository di Google Code e testare l'intera applicazione in locale.&lt;/p&gt;

&lt;p&gt;Questo è l'indirizzo&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lyra.latenight-coding.com/"&gt;http://lyra.latenight-coding.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Con l'occasione ho aggiunto un componente &lt;em&gt;archive&lt;/em&gt; per visualizzare un archivio mensile degli articoli (vedere menù nella colonna destra), modificato la 'byline' dell'articolo per includere l'autore oltre alla data e fatto altri aggiustamenti. Tutto si trova nelle revisioni 24, 25, 26, 27.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-354838157107144814?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xJFzZVzKJIJpnPb8DPViGHavPOI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/HtIfGmc1kiw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/354838157107144814/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/demo-lyra-online.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/354838157107144814?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/354838157107144814?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/12/demo-lyra-online.html" title="Demo Lyra online" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CUUAQnk5eSp7ImA9WxNaEUg.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5616163960148133439</id><published>2009-11-25T13:41:00.000+01:00</published><updated>2009-11-25T13:47:23.721+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-25T13:47:23.721+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, gestione utenti backend</title><content type="html">&lt;p&gt;Arrivati a questo punto è necessario iniziare lo sviluppo delle funzione di gestione utenti. Chi ha scaricato l'applicazione dal repository avrà notato che non viene richiesto alcun login per accedere al backend, con la gestione gestione utenti saranno implementate le funzioni di controllo dell'accesso sia al backend che a quelle azioni del frontend che richiedono un'autenticazione (ad esempio inserimento e modifica articoli).&lt;/p&gt;

&lt;p&gt;Seguirò le linee del tutorial ufficiale che prevede di utilizzare il plugin &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt;. Per prima cosa si installa il plugin con&lt;/p&gt;
&lt;pre&gt;
./symfony plugin:install sfDoctrineGuardPlugin
&lt;/pre&gt;

&lt;p&gt;Si attiva il plugin.&lt;/p&gt;

&lt;pre&gt;
config/ProjectConfiguration.class.php

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this-&amp;gt;enablePlugins(array('sfDoctrinePlugin','sfDoctrineGuardPlugin'));
    $this-&amp;gt;disablePlugins(array('sfPropelPlugin'));
  }
}
&lt;/pre&gt;
&lt;p&gt;Questo almeno è come ho fatto io. Se nel metodo &lt;strong&gt;setup()&lt;/strong&gt; della classe  &lt;strong&gt;ProjectConfiguration&lt;/strong&gt; si utilizza il metodo &lt;strong&gt;enableAllPluginsExcept()&lt;/strong&gt; può non essere necessario abilitare singolarmente il plugin.&lt;/p&gt;

&lt;p&gt;Si generano le classi del modello messe a disposizione dal plugin e si pulisce la cache di symfony come al solito quando si creano nuove classi.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;

&lt;p&gt;Poi si modifica la classe &lt;strong&gt;myUser&lt;/strong&gt; e la si fa derivare dalla classe base che ci viene fornita dal plugin.&lt;/p&gt;

&lt;pre&gt;
apps/backend/lib/myUser.class.php

class myUser extends sfGuardSecurityUser
{
}
&lt;/pre&gt;

Nella configurazione si abilitano i moduli di &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; e si imposta l'azione che viene eseguita quando un utente richiede il login (&lt;strong&gt;signin&lt;/strong&gt;).

&lt;pre&gt;
apps/backend/config/settings.yml
...
all:
  .settings:
    enabled_modules: [default, sfGuardAuth, sfGuardUser, sfGuardGroup]
    ...
  .actions:
    login_module: sfGuardAuth
    login_action: signin
...
&lt;/pre&gt;

&lt;p&gt;Adesso abbiamo quello che ci serve per attivare la funzione di login al backend.&lt;/p&gt;

&lt;pre&gt;
apps/backend/config/security.yml

default:
  is_secure: on
&lt;/pre&gt;

&lt;p&gt;Naturalmente a questo punto bisogna creare almeno un utente, lo si può fare da linea di comando con&lt;/p&gt;

&lt;pre&gt;./symfony guard:create-user admin admin&lt;/pre&gt;

&lt;p&gt;Fino a che restiamo in locale possiamo utilizzare &lt;em&gt;admin&lt;/em&gt; come nome utente e password. Promuoviamo l'utente a superamministratore.&lt;/p&gt;

&lt;pre&gt;./symfony guard:promote admin&lt;/pre&gt;

&lt;p&gt;Il layout (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/backend/templates/layout.php"&gt;layout.php&lt;/a&gt; in &lt;em&gt;apps/backend/templates&lt;/em&gt;) del backend deve essere modificato per impedire la visualizzazione del menù principale agli utenti non autenticati ed aggiungervi un link per il logout e i link alla gestione utenti e gruppi utente che sono messe a disposizione dai moduli &lt;strong&gt;sfGuardUser&lt;/strong&gt; e &lt;strong&gt;sfGuardGroup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Questi moduli possono essere personalizzati con lo stesso metodo utilizzato per i moduli generati nell'applicazione impostando cioè i parametri di configurazione nel file&lt;em&gt;generator.yml&lt;/em&gt;. Però non conviene modificare direttamente i file installati dal plugin in &lt;em&gt;plugins/sfDoctrineGuardPlugin&lt;/em&gt; e sottocartelle, ma creare una cartella per ogni modulo in &lt;em&gt;apps/backend/modules&lt;/em&gt; e inserirvi le personalizzazioni volute. In questo modo potremo fare gli aggiornamenti di &lt;strong&gt;sfDoctrineGuardPlugin&lt;/strong&gt; senza sovrascrivere file che abbiamo modificato.&lt;/p&gt;

&lt;p&gt;Ho creato una cartella &lt;em&gt;apps/backend/modules/sfGuardUser/config&lt;/em&gt; e vi ho copiato il file &lt;em&gt;generator.yml&lt;/em&gt; che si trova in &lt;em&gt;plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config&lt;/em&gt;. Ho quindi modificato il file per personalizzare l'aspetto della lista utenti.&lt;/p&gt;

&lt;p&gt;La stessa cosa è stata fatta per il modulo &lt;strong&gt;sfGuardGroup&lt;/strong&gt;. Le personalizzazioni al momento sono minime, il contenuto dei file può essere consultato sul repository. In modo analogo è possibile personalizzare la classe actions e i template, esempi si trovano nella documentazione di sfDoctrineGuardPlugin.&lt;/p&gt;

&lt;p&gt;Ho creato un file &lt;em&gt;data/fixtures/users.yml&lt;/em&gt; per l'inserimento di un utente superamministratore nei dati di esempio. In questo modo durante lo sviluppo non si è costretti ad eseguire i comandi &lt;strong&gt;guard:create-user&lt;/strong&gt; e &lt;strong&gt;guard:promote&lt;/strong&gt; ogni volta che si ricreano le tabelle con &lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
data/fixtures/users.yml

sfGuardUser:
  admin:
    username:       admin
    password:       admin
    is_super_admin: true
&lt;/pre&gt;
&lt;p&gt;Ho inoltre definito le relazioni articolo-utente &lt;/p&gt;
&lt;pre&gt;
config/doctrine/schema.yml

LyraArticle:
...
  relations:
    ...
    ArticleCreatedBy:
      class: sfGuardUser
      local: created_by
      foreign: id
      foreignAlias: CreatedArticles
    ArticleUpdatedBy:
      class: sfGuardUser
      local: updated_by
      foreign: id
      foreignAlias: UpdatedArticles
&lt;/pre&gt;
&lt;p&gt;e relazioni analoghe nella tabella etichette&lt;/p&gt;

&lt;pre&gt;
LyraLabel:
...
  relations:
      ...
      LabelCreatedBy:
        class: sfGuardUser
        local: created_by
        foreign: id
        foreignAlias: CreatedLabels
      LabelUpdatedBy:
        class: sfGuardUser
        local: updated_by
        foreign: id
        foreignAlias: UpdatedLabels
&lt;/pre&gt; 

&lt;p&gt;I file &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/fixtures/articles.yml"&gt;articles.yml&lt;/a&gt; e &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/data/fixtures/labels.yml"&gt;labels.yml&lt;/a&gt; in &lt;em&gt;data/fixtures&lt;/em&gt; sono stati modificati in modo che quando si creano i dati di esempio le chiavi esterne dei record siano valorizzate opportunamente in base alle relazioni appena definite.&lt;/p&gt;

&lt;p&gt;Tutto questo è incluso nella &lt;strong&gt;revisione 23&lt;/strong&gt;. Visto che abbiamo modificato &lt;em&gt;schema.yml&lt;/em&gt; e i file dei dati di esempio, dopo il checkout è necessario eseguire:&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:build-all-reload
./symfony cc
&lt;/pre&gt;

&lt;p&gt;A questo punto possiamo effettuare il login al backend.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Login e password: admin.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5616163960148133439?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/ml0IaDT_UBpk8F2Zs-QjFKtJmbk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/3pIsuEzkO0w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5616163960148133439/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-utenti-backend.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5616163960148133439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5616163960148133439?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-utenti-backend.html" title="Lyra, gestione utenti backend" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;A0IFSXk9cCp7ImA9WxNbGUs.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-6581352395802748996</id><published>2009-11-23T10:43:00.000+01:00</published><updated>2009-11-23T10:45:18.768+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-23T10:45:18.768+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, backend cataloghi ed etichette</title><content type="html">&lt;p&gt;Credo sia chiaro a chiunque abbia seguito gli articoli precedenti che lo sviluppo del backend di un'applicazione symfony segue linee abbastanza standard: le funzioni base di ogni modulo come la visualizzazione dell'elenco record e le funzioni di inserimento, modifica, cancellazione sono generate automaticamente con il comando &lt;strong&gt;doctrine:generate-admin&lt;/strong&gt;; le personalizzazioni per la maggior parte si realizzano impostando parametri nel file di configurazione (&lt;em&gt;generator.yml&lt;/em&gt; nella cartella &lt;em&gt;config&lt;/em&gt; del modulo) anche se è naturalmente possibile scrivere proprio codice per realizzare funzionalità aggiuntive o che comunque si discostano dallo standard.&lt;/p&gt;

&lt;p&gt;Per questo motivo non mi soffermerò nei dettagli dello sviluppo della parte restante del backend di Lyra perché si finirebbe per ripetere quanto già detto in precedenza. Di volta in volta metterò in evidenza solo gli aspetti su cui a mio parere vale la pena soffermarsi, d'altra parte il codice completo è sempre disponibile nel repository su Google Code.&lt;/p&gt;

&lt;h2&gt;Gestione cataloghi&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:generate-admin backend LyraCatalog --module=catalog&lt;/pre&gt;

&lt;p&gt;Dopo la consueta personalizzazione di &lt;em&gt;generator.yml&lt;/em&gt; si ottiene questo risultato.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s1600/backend-cataloghi.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 158px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s320/backend-cataloghi.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5407100187057231058" /&gt;&lt;/a&gt;
&lt;p&gt;Il contenuto delle colonne &lt;strong&gt;Etichette&lt;/strong&gt; e &lt;strong&gt;Pubblicato&lt;/strong&gt; è generato da due partial (&lt;em&gt;_labels.php&lt;/em&gt; e &lt;em&gt;_published.php&lt;/em&gt; in &lt;em&gt;apps/backend/modules/catalog/templates&lt;/em&gt;): il primo visualizza il contatore delle etichette inserite nel catalogo e mostra un link per accedere alla relativa lista.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/catalog/templates/_labels.php

&amp;lt;?php
    echo link_to($lyra_catalog-&amp;gt;countLabels()-1,'@lyra_label_label?id='.$lyra_catalog-&amp;gt;getId());
?&amp;gt;&amp;amp;nbsp;(&amp;lt;?php echo link_to(__('LINK_SHOW_LABELS'),'@lyra_label_label?id='.$lyra_catalog-&amp;gt;getId());?&amp;gt;)
&lt;/pre&gt;

&lt;p&gt;Il secondo genera i link per le icone usate per modificare lo stato pubblicato / non pubblicato del catalogo e funziona nel modo già visto per la gestione articoli e commenti.&lt;/p&gt;

&lt;p&gt;La configurazione del form per l'inserimento e modifica di un catalogo (classe &lt;strong&gt;LyraCatalogForm&lt;/strong&gt;) non presenta particolarità. Ogni catalogo potrà essere associato ad uno o più tipi di contenuto, selezionabili da una lista di checkbox nel form; non è necessario scrivere codice particolare per creare l'insieme delle checkbox in quanto Doctrine genera automaticamente un widget &lt;strong&gt;sfWidgetFormDoctrineChoiceMany&lt;/strong&gt; in base alla relazione cataloghi - tipi di contenuto definita in &lt;em&gt;schema.yml&lt;/em&gt;. Basta impostare l'attributo &lt;strong&gt;expanded&lt;/strong&gt; a &lt;em&gt;true&lt;/em&gt; in &lt;strong&gt;configure()&lt;/strong&gt; per avere un'insieme di checkbox al posto della lista di selezione.&lt;/p&gt;

&lt;p&gt;Quando si crea un nuovo catalogo viene anche inserito nella tabella etichette un record che costituisce la 'radice' dell'albero delle etichette appartenenti al catalogo. Questo viene fatto nel metodo &lt;strong&gt;doSave()&lt;/strong&gt; di &lt;strong&gt;LyraCatalogForm&lt;/strong&gt; che viene eseguito da symfony quando si salva il record.&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/LyraCatalogForm.class.php

class LyraCatalogForm extends BaseLyraCatalogForm
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at'], $this['locked_by']);
    $this-&amp;gt;widgetSchema['name']-&amp;gt;setLabel('NAME');
    $this-&amp;gt;widgetSchema['description']-&amp;gt;setLabel('DESCRIPTION');
    $this-&amp;gt;widgetSchema['is_active']-&amp;gt;setLabel('IS_ACTIVE');
    $this-&amp;gt;widgetSchema['catalog_content_types_list']-&amp;gt;setLabel('CATALOG_CONTENT_TYPES');

    $this-&amp;gt;widgetSchema['catalog_content_types_list']-&amp;gt;setOption('expanded', true);
  }
  protected function doSave($con = null)
  {
    $savingnew = $this-&amp;gt;isNew();
    parent::doSave($con);

    if ($savingnew) {
      $label = new LyraLabel();
      $label-&amp;gt;setName($this-&amp;gt;object-&amp;gt;getName());

      $label-&amp;gt;setCatalogId($this-&amp;gt;object-&amp;gt;getId());
      $label-&amp;gt;save();
      $treeObject = Doctrine::getTable('LyraLabel')-&amp;gt;getTree();
      $treeObject-&amp;gt;createRoot($label);
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Ho eseguito a questo punto il commit della &lt;strong&gt;revisione 21&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Gestione etichette&lt;/h2&gt;

&lt;pre&gt;./symfony doctrine:generate-admin backend LyraLabel --module=label&lt;/pre&gt;

&lt;p&gt;Non esiste un link nel menù principale che porti ad una lista di tutte le etichette, è possibile visualizzare le etichette di ogni catalogo dal link 'Mostra' nella colonna &lt;strong&gt;Etichette&lt;/strong&gt; della lista cataloghi.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SwnhIGgaBCI/AAAAAAAAAIw/hPCHIu4o5GE/s1600/label-backend.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 198px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SwnhIGgaBCI/AAAAAAAAAIw/hPCHIu4o5GE/s320/label-backend.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5407100356936729634" /&gt;&lt;/a&gt;
&lt;p&gt;La colonna &lt;strong&gt;Nome&lt;/strong&gt; non mostra direttamente il contenuto di un campo, ma il risultato del metodo &lt;strong&gt;getIndentName()&lt;/strong&gt; della classe &lt;strong&gt;LyraLabel&lt;/strong&gt; che fa sì che il nome venga indentato a seconda del livello che l'etichetta occupa nell'albero.&lt;/p&gt;
&lt;pre&gt;
lib/model/doctrine/LyraLabel.class.php

class LyraLabel extends BaseLyraLabel
{
  ...
  function getIndentName()
  {
    $indent = $this-&amp;gt;level-1;
    if($indent &amp;lt; 0) {
      $indent = 0;
    }
    return str_repeat('-- ', $indent).$this-&amp;gt;name;
  }
}
&lt;/pre&gt;
&lt;p&gt;La colonna &lt;strong&gt;Ordina&lt;/strong&gt; contiene due frecce per modificare l'ordine di un'etichetta rispetto alle etichette di pari livello (i nodi fratelli). In questo caso si utilizza un partial per generare il contenuto della colonna.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/label/templates/_order.php

&amp;lt;?php
$node = $lyra_label-&amp;gt;getNode();

if($node-&amp;gt;hasNextSibling()) {
  echo link_to(image_tag('backend/arrow-down.png', array('alt' =&amp;gt; __('LINK_T_MOVE_DOWN'))),'label/move?id='.$lyra_label-&amp;gt;getId().'&amp;amp;dir=0',array('title' =&amp;gt; __('LINK_T_MOVE_DOWN')));
}
if($node-&amp;gt;hasPrevSibling()) {
  echo link_to(image_tag('backend/arrow-up.png', array('alt' =&amp;gt; __('LINK_T_MOVE_UP'))),'label/move?id='.$lyra_label-&amp;gt;getId().'&amp;amp;dir=1',array('title' =&amp;gt; __('LINK_T_MOVE_UP')));
}
&lt;/pre&gt;
I link sono collegati ad un'azione &lt;strong&gt;move&lt;/strong&gt; implementata in labelActions. 
&lt;pre&gt;
apps/backend/modules/label/actions/actions.class.php

class labelActions extends autoLabelActions
{
  ...  
  public function executeMove(sfWebRequest $request)
  {
    $record = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    
    switch($request-&amp;gt;getParameter('dir')) {
      case 0:
        $next = $record-&amp;gt;getNode()-&amp;gt;getNextSibling();
        if($next) {
          $record-&amp;gt;getNode()-&amp;gt;moveAsNextSiblingOf($next);
        }
        break;
      case 1:
        $prev = $record-&amp;gt;getNode()-&amp;gt;getPrevSibling();
        if($prev) {
          $record-&amp;gt;getNode()-&amp;gt;moveAsPrevSiblingOf($prev);
        }
        break;
    }
    $this-&amp;gt;redirect('@lyra_label_label');
  }
  ...
}
&lt;/pre&gt;
Per quanto riguarda l'utilizzo dei metodi &lt;strong&gt;getNode()&lt;/strong&gt;, &lt;strong&gt;hasNextSibling()&lt;/strong&gt;, &lt;strong&gt;hasPrevSibling()&lt;/strong&gt;, &lt;strong&gt;moveAsNextSiblingOf()&lt;/strong&gt;, &lt;strong&gt;moveAsPrevSiblingOf()&lt;/strong&gt; utilizzati nel partial e nel metodo &lt;strong&gt;executeMove()&lt;/strong&gt; della classe action rimando alla documentazione ufficiale di Doctrine sui &lt;a href="http://www.doctrine-project.org/documentation/manual/1_1/en/hierarchical-data"&gt;nested set&lt;/a&gt;.

&lt;p&gt;L'unica particolarità che presenta la configurazione del form per l'inserimento e modifica di un'etichetta(classe &lt;strong&gt;LyraLabelForm&lt;/strong&gt;) è data dalla generazione della lista per la selezione dell'etichetta 'padre'. Il problema è che mentre una &lt;strong&gt;nuova&lt;/strong&gt; etichetta può essere inserita come &lt;strong&gt;figlia&lt;/strong&gt; di una qualsiasi delle etichette esistenti, quando si &lt;strong&gt;modifica&lt;/strong&gt; un'etichetta non si può selezionare come padre nessuna delle etichette sue &lt;strong&gt;discendenti&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Serve un esempio. Data una struttura di questo tipo&lt;/p&gt;

&lt;pre&gt;
PHP
-- Framework
---- Symfony
---- CakePHP
&lt;/pre&gt;
&lt;p&gt;Quando si modifica l'etichetta &lt;em&gt;PHP&lt;/em&gt; non deve essere possibile selezionare come nuovo padre né &lt;em&gt;Framework&lt;/em&gt;, nè &lt;em&gt;Symfony&lt;/em&gt;, né &lt;em&gt;CakePHP&lt;/em&gt;, perché in questo modo si distruggerebbe la struttura dell'albero. Questo controllo è fatto nel metodo &lt;strong&gt;configure()&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/LyraLabelForm.class.php

class LyraLabelForm extends BaseLyraLabelForm
{
  public function configure()
  {
    ...
    $query = Doctrine_Query::create()-&amp;gt;from('LyraLabel l');

    if($this-&amp;gt;isNew()) {
        $catalog_id = sfContext::getInstance()-&amp;gt;getUser()-&amp;gt;getAttribute('lyra_catalog_id');
        if($catalog_id) {
          $this-&amp;gt;setDefault('catalog_id', $catalog_id);
          $query-&amp;gt;where('l.catalog_id = ?', $catalog_id);
        }
    } else {
        $query-&amp;gt;where('l.catalog_id = ? AND (l.lft &amp;lt; ? OR l.rgt &amp;gt; ?)', array($this-&amp;gt;object-&amp;gt;getCatalogId(), $this-&amp;gt;object-&amp;gt;getLft(), $this-&amp;gt;object-&amp;gt;getRgt()));
    }

    $this-&amp;gt;widgetSchema['parent_id'] = new sfWidgetFormDoctrineChoice(array('model'=&amp;gt;'LyraLabel', 'order_by'=&amp;gt;array('root_id, lft', ''), 'method'=&amp;gt;'getIndentName', 'query'=&amp;gt;$query));
    $this-&amp;gt;validatorSchema['parent_id'] = new sfValidatorDoctrineChoice(array('required'=&amp;gt;false, 'model'=&amp;gt;'LyraLabel'));
    ...
  }
&lt;/pre&gt;
&lt;p&gt;In inserimento si mostra nella lista tutto l'albero delle etichette appartenenti al catalogo, in modifica si escludono i discendenti del record etichetta che stiamo modificando. Da dove viene fuori la query nell'&lt;em&gt;else&lt;/em&gt;? Per capirlo occorre conoscere più nei dettagli come viene costruito un nested set e in particolare la funzione dei campi &lt;em&gt;lft&lt;/em&gt; e &lt;em&gt;rgt&lt;/em&gt;: una spiegazione che personalmente ho trovato utile è &lt;a href="http://dev.mysql.com/tech-resources/articles/hierarchical-data.html"&gt;questa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Per riassumere la gestione etichette nel backend: con le frecce nell'elenco si modifica l'ordine tra etichette sullo stesso livello, entrando in modifica del record si può spostare un'etichetta e tutti i suoi discendenti (un ramo) sotto un'altra. Per ora è così, questa parte della gestione è un po' complessa e richiederà altro lavoro.&lt;/p&gt;

&lt;p&gt;Chi volesse giocare un po' con le nuove funzioni può allinearsi alla &lt;strong&gt;revisione 22&lt;/strong&gt; di Google Code. Ricordo che per entrare nel backend basta inserire nel browser l'indirizzo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php o http://lyra/backend.php&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Questo se si è configurato un virtual host secondo le istruzioni date nei primi articoli.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-6581352395802748996?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/PsaM4Z2SZ6zB0OouNzb2Mbbf6FI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/7FJquaES-l4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/6581352395802748996/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-backend-cataloghi-ed-etichette.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6581352395802748996?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/6581352395802748996?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-backend-cataloghi-ed-etichette.html" title="Lyra, backend cataloghi ed etichette" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_unGpwtr1q-o/Swng-Np-lNI/AAAAAAAAAIo/t3h-6F5mMwA/s72-c/backend-cataloghi.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkUASX84fip7ImA9WxNbE0U.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4896691106828327948</id><published>2009-11-16T15:01:00.002+01:00</published><updated>2009-11-16T15:04:08.136+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-16T15:04:08.136+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Utilizzare symfony in Windows con WampServer</title><content type="html">&lt;p&gt;Ho pensato potesse essere utile a chi volesse seguire lo sviluppo di Lyra sotto Windows una breve guida su come configurare symfony per l'utilizzo in questo sistema operativo.&lt;/p&gt;

&lt;p&gt;Servono innanzi tutto Apache, MySql e PHP. Il modo più veloce per installarli in Windows è utilizzare un 'pacchetto' come Wamp, Xampp o EasyPHP; la procedura riportata di seguito è stata provata su &lt;a href="http://www.wampserver.com/en/"&gt;WampServer&lt;/a&gt; versione 2.0-i, per chi utilizza uno degli altri prodotti la sostanza non cambia, ma possono esserci differenze nei percorsi dei file di configurazione di Apache e dell'eseguibile PHP.&lt;/p&gt;

&lt;p&gt;Serve poi un client subversion. &lt;a href="http://tortoisesvn.tigris.org/"&gt;TortoiseSVN&lt;/a&gt; è facile da utilizzare e gratuito.&lt;/p&gt;

&lt;p&gt;Una volta installate queste componenti creiamo una cartella per l'applicazione, ad esempio &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt;.&lt;/p&gt; 

&lt;h2&gt;Checkout da repository&lt;/h2&gt;

&lt;p&gt;Come prima cosa conviene scaricare dal repository su Google Code la versione più recente dell'applicazione. In Windows Explorer fare clic con il pulsante destro sulla cartella &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt; e selezionare &lt;em&gt;SVN Checkout&lt;/em&gt; (se non c'è l'opzione non si è installato correttamente TortoiseSVN); nella finestra di dialogo inserire:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;URL of repository: http://lyra-cms.googlecode.com/svn/trunk/&lt;/li&gt;
  &lt;li&gt;Checkout directory: C:\sfprojects\lyra&lt;/li&gt;
  &lt;li&gt;Checkout depth: fully recursive (la casella 'Omit external' deve essere non selezionata)&lt;/li&gt;
  &lt;li&gt;Head revision&lt;/li&gt;
&lt;/ul&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s1600-h/checkout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 248px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s320/checkout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404493084455259442" /&gt;&lt;/a&gt;
&lt;p&gt;Il primo checkout richiede del tempo perché deve essere scaricato tutto il framework. Gli aggiornamenti a versioni successive saranno più veloci.&lt;/p&gt;


&lt;h2&gt;Configurazione virtual host&lt;/h2&gt;

&lt;p&gt;Personalmente trovo comodo lavorare in locale attraverso l'utilizzo dei virtual host: in questo modo si possono gestire molti progetti senza dover creare una miriade di sotto-cartelle nella Document Root di Apache.&lt;/p&gt;

&lt;p&gt;Assumo che l'installazione di Wamp sia stata fatta nella cartella predefinita &lt;em&gt;C:\wamp&lt;/em&gt;. Aprire con un editor di testi il file &lt;em&gt;httpd.conf&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\wamp\bin\apache\Apache2.2.11\conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Il numero di versione di Apache può essere diverso a seconda della versione di Wamp installata. Trovare questa riga&lt;/p&gt;
&lt;pre&gt;#Include conf/extra/httpd-vhosts.conf&lt;/pre&gt;

&lt;p&gt;e decommentarla rimuovendo il '#' iniziale, salvare poi il file.&lt;/p&gt;

&lt;p&gt;Aprire il file &lt;em&gt;httpd-vhosts.conf&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;code&gt;C:\wamp\bin\apache\Apache2.2.11\conf\extra&lt;/code&gt;

&lt;p&gt;Accertarsi che sia presente e attiva (cioè non preceduta da '#') la riga&lt;/p&gt;

&lt;pre&gt;NameVirtualHost *:80&lt;/pre&gt;

&lt;p&gt;Aggiungere alla fine del file&lt;/p&gt;

&lt;pre&gt;
&amp;lt;VirtualHost *:80&amp;gt;
    ServerName localhost
    DocumentRoot "c:/wamp/www/"
&amp;lt;/VirtualHost&amp;gt;

&amp;lt;VirtualHost *:80&amp;gt;  
ServerName lyra  
DocumentRoot "c:/sfprojects/lyra/web/"  
DirectoryIndex index.php
&amp;lt;Directory "c:/sfprojects/lyra/web/"&amp;gt;
    AllowOverride All
    Allow from All
&amp;lt;/Directory&amp;gt;

Alias /sf "c:/sfprojects/lyra/lib/vendor/symfony/data/web/sf"
&amp;lt;Directory "c:/sfprojects/lyra/lib/vendor/symfony/data/web/sf"&amp;gt;
 AllowOverride All
 Allow from All
&amp;lt;/Directory&amp;gt;

Alias /sfDoctrinePlugin "c:/sfprojects/lyra/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web"
&amp;lt;Directory "c:/sfprojects/lyra/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/web"&amp;gt;
 AllowOverride All
 Allow from All
&amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Aprire il file &lt;em&gt;hosts&lt;/em&gt; che si trova nella cartella&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\WINDOWS\system32\drivers\etc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Aggiungere subito dopo&lt;/p&gt;

&lt;pre&gt;127.0.0.1 localhost&lt;/pre&gt;

&lt;p&gt;questa riga&lt;/p&gt;

&lt;pre&gt;127.0.0.1 lyra&lt;/pre&gt;

&lt;p&gt;Al posto di &lt;em&gt;lyra&lt;/em&gt; si può ovviamente usare il nome che si vuole basta che corrisponda a &lt;strong&gt;ServerName&lt;/strong&gt; nella configurazione del virtual host. A questo punto bisogna eseguire Wamp, se è già in esecuzione si deve comunque riavviare Apache dal menù disponibile cliccando l'icona di notifica di Wamp.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCeva8xjNI/AAAAAAAAAIQ/ouAtL8HvlE8/s1600-h/wamp1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 202px; height: 265px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCeva8xjNI/AAAAAAAAAIQ/ouAtL8HvlE8/s320/wamp1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404494090369862866" /&gt;&lt;/a&gt;
&lt;h2&gt;Attivazione mod_rewrite&lt;/h2&gt;

&lt;p&gt;Bisogna anche attivare il modulo Apache &lt;em&gt;mod_rewrite&lt;/em&gt; per gestire URL semplificate (SEF) in symfony. Basta fare clic sull'icona di WampServer nell'area di notifica, selezionare l'opzione del menù Apache &gt;&gt; Apache modules &gt;&gt; scorrere la lista dei moduli fino a trovare &lt;em&gt;rewrite_module&lt;/em&gt; e selezionarlo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SwCuqY4j6GI/AAAAAAAAAIg/rH6yUZZsUqE/s1600-h/mod-rewrite.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 201px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SwCuqY4j6GI/AAAAAAAAAIg/rH6yUZZsUqE/s320/mod-rewrite.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404511596102019170" /&gt;&lt;/a&gt;
&lt;h2&gt;Configurazione PHP&lt;/h2&gt;

&lt;p&gt;Durante lo sviluppo di un'applicazione symfony è frequente eseguire script PHP da riga di comando. Per prima cosa copiare nella cartella &lt;em&gt;C:\sfprojects\lyra&lt;/em&gt; il file &lt;em&gt;symfony.bat&lt;/em&gt; che si trova nella cartella &lt;/p&gt;

&lt;p&gt;&lt;code&gt;C:\sfprojects\lyra\lib\vendor\symfony\data\bin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Occorre poi configurare una variabile di ambiente &lt;strong&gt;PHP_COMMAND&lt;/strong&gt; che serve a far conoscere allo script &lt;em&gt;symfony.bat&lt;/em&gt; il percorso dell'eseguibile PHP che nel nostro caso è&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\wamp\bin\php\php5.3.0\php.exe&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Il numero di versione di PHP può essere diverso a seconda della versione di Wamp installata.&lt;/p&gt;
&lt;p&gt;In Windows XP (io a quello sono rimasto): Pannello di controllo &gt;&gt; Sistema &gt;&gt; tab Avanzate &gt;&gt; in fondo alla finestra di dialogo premere il pulsante 'Variabili di ambiente', nella finestra successiva premere 'Nuovo' sotto 'Variabili utente' (o sotto 'Variabili di sistema' se si preferisce) e inserire&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nome variabile: PHP_COMMAND&lt;/li&gt;
  &lt;li&gt;Valore variabile: C:\wamp\bin\php\php5.3.0\php.exe&lt;/li&gt;
&lt;/ul&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCegIK6dRI/AAAAAAAAAII/RTv9DetQd3k/s1600-h/variabile-sistema.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 210px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SwCegIK6dRI/AAAAAAAAAII/RTv9DetQd3k/s320/variabile-sistema.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404493827630855442" /&gt;&lt;/a&gt;
&lt;p&gt;Chiudere tutto con OK. Per verificare di aver fatto tutto bene aprire una finestra terminale e scrivere&lt;/p&gt;

&lt;pre&gt;
cd c:\sfprojects\lyra
symfony -V
&lt;/pre&gt;
&lt;p&gt;Deve comparire il numero della versione di symfony.&lt;/p&gt;

&lt;h2&gt;Configurazione database MySql&lt;/h2&gt;

&lt;p&gt;Da &lt;strong&gt;phpMyAdmin &lt;/strong&gt;(con Wamp si lancia navigando con il browser all'indirizzo &lt;em&gt;http://localhost/phpmyadmin/&lt;/em&gt;) creare un database ed un nuovo utente assegnandogli i privilegi sul database.&lt;/p&gt;

&lt;p&gt;Modificare il file &lt;em&gt;c:\sfprojects\lyra\config\databases.yml&lt;/em&gt; inserendo il nome database, utente e password che si sono appaena creati.&lt;/p&gt;


&lt;p&gt;Tornare alla finestra terminale, accertarsi di essere ancora in &lt;em&gt;c:\sfprojects\lyra&lt;/em&gt; e scrivere&lt;/p&gt;
&lt;pre&gt;
symfony doctrine:build-all-reload
&lt;/pre&gt;
&lt;p&gt;Dare conferma all'avviso che le tabelle del database saranno ricreate. Se non ci sono errori nella configurazione del database, si vedranno una serie di messaggi che avvisano del progresso delle varie operazioni (generazione delle classi del modello, creazione tabelle, caricamento fixtures). Alla fine scrivere&lt;/p&gt;
&lt;pre&gt;
symfony cc
&lt;/pre&gt;

&lt;p&gt;Aprendo il browser e digitando &lt;em&gt;http://lyra/frontend_dev.php&lt;/em&gt; ci si trova sulla prima pagina dell'applicazione. Per entrare nel backend l'indirizzo è &lt;em&gt;http://lyra/backend_dev.php&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;La procedura sembra complessa, ma la gran parte del lavoro (configurazione virtual host, creazione database, impostazione PHP) si fa una volta sola. Il comando &lt;strong&gt;doctrine:build-all-reload&lt;/strong&gt; va eseguito obbligatoriamente dopo il primo checkout, in seguito quando ci si aggiorna alle successive revisioni non è necessario a meno che non vi siano state modifiche alla struttura del database. Eseguire il comando comunque non fa danno, ma va tenuto presente che le tabelle vengono cancellate, ricreate e ricaricati i dati di prova (fixtures), quindi si perdono eventuali dati inseriti.&lt;/p&gt;

&lt;p&gt;La pulizia della cache di symfony con il comando 'cc' va eseguita ogni volta dopo un checkout o update dal repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tortoise SVN&lt;/strong&gt; come ogni altro client subversion consente di creare una copia di lavoro locale aggiornata non solo alla versione più recente dell'applicazione, ma ad un qualsiasi numero di revisione che indicheremo nella finestra di dialogo dell'opzione checkout.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCe9t3zmTI/AAAAAAAAAIY/kIWdF3y-_qI/s1600-h/checkout1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 250px;" src="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCe9t3zmTI/AAAAAAAAAIY/kIWdF3y-_qI/s320/checkout1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404494335967467826" /&gt;&lt;/a&gt;
&lt;p&gt;Come detto a suo tempo, questo permette di ripercorrere l'intero sviluppo seguendo gli articoli pubblicati dall'inizio, ma anche da un qualsiasi momento successivo in quanto in ogni articolo è indicato il numero di revisione a cui si riferisce il codice.&lt;/p&gt;

&lt;p&gt;Tutta la procedura è stata testata, chi dovesse incontrare qualche problema lasci un commento e proverò ad aiutarlo.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4896691106828327948?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/o3B9xDkFZBM0yip9ynIJpufiYjA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/tVHYTF9vtok" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4896691106828327948/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/utilizzare-symfony-in-wampserver.html#comment-form" title="2 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4896691106828327948?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4896691106828327948?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/utilizzare-symfony-in-wampserver.html" title="Utilizzare symfony in Windows con WampServer" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_unGpwtr1q-o/SwCd03oCzTI/AAAAAAAAAIA/INIPO_XG9a8/s72-c/checkout.png" height="72" width="72" /><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;D0IERnc-cCp7ImA9WxNbEE4.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-3181112920595286979</id><published>2009-11-12T15:15:00.000+01:00</published><updated>2009-11-12T15:18:27.958+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-12T15:18:27.958+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra,  gestione commenti backend</title><content type="html">&lt;p&gt;Iniziamo lo sviluppo della gestione commenti nel backend. Prima di tutto creiamo il modulo.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:generate-admin backend LyraComment --module=comment
&lt;/pre&gt;

&lt;p&gt;Poi passiamo alla personalizzazione del file &lt;em&gt;generator.yml&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
config:
      ...
      list:
        title: TITLE_COMMENTS
        display: [comment_article,author_name,content,_published,created_at]
        fields:
          comment_article: {label: TH_ARTICLE_TITLE}
          author_name: {label: TH_AUTHOR_NAME}
          author_email: {label: TH_AUTHOR_EMAIL}
          author_url: {label: TH_AUTHOR_URL}
          content: {label: TH_CONTENT}
          created_at: {label: TH_CREATED_AT, date_format: dd.MM.yy hh:ss}
          updated_at: {label: TH_UPDATED_AT, date_format: dd.MM.yy hh:ss}
        batch_actions:
          _delete: ~
          publish: {label: PUBLISH}
          unpublish: {label: UNPUBLISH}
        table_method: getBackendItemsQuery
...
&lt;/pre&gt;
&lt;p&gt;Vediamo i vari 'blocchi' uno ad uno.&lt;/p&gt;
&lt;h2&gt;Elenco commenti&lt;/h2&gt;
&lt;p&gt;In &lt;strong&gt;list&lt;/strong&gt; definiamo la configurazione delle lista commenti: impostiamo il titolo della pagina (&lt;strong&gt;title&lt;/strong&gt;) in una forma che dovrà essere tradotta nei file di lingua, l'elenco delle colonne (&lt;strong&gt;display&lt;/strong&gt;) con le relative intestazioni e formato (&lt;strong&gt;fields&lt;/strong&gt;) e le azioni batch &lt;strong&gt;delete&lt;/strong&gt; (predefinita), &lt;strong&gt;publish&lt;/strong&gt; e &lt;strong&gt;unpublish&lt;/strong&gt; che dovremo implementare in modo del tutto analogo a quanto fatto per la gestione articoli (&lt;a href="http://sviluppare-in-rete.blogspot.com/2009/11/symfony-personalizzare-il-backend.html"&gt;simfony, personalizzare il backend&lt;/a&gt;).&lt;/p&gt;
 
&lt;p&gt;Da notare la presenza di &lt;em&gt;comment_article&lt;/em&gt; nella lista dei campi da mostrare nelle colonne dell'elenco: non è un campo della tabella Commenti, ma l'identificatore di una relazione impostata nello schema, solo che nel file di configurazione si separano le parole con il carattere sottolineato, nella definizione dello schema si utilizza il 'camel case'.&lt;/p&gt;

&lt;pre&gt;
config/doctrine/schema.yml

LyraComment:
 ...
  relations:
    CommentArticle:
      class: LyraArticle
      local: article_id
      foreign: id
      foreignAlias: ArticleComments
      onDelete: CASCADE
&lt;/pre&gt;

&lt;p&gt;In questo modo possiamo avere nell'elenco il titolo dell'articolo invece dell'ID.&lt;/p&gt;

&lt;p&gt;La colonna &lt;strong&gt;_published&lt;/strong&gt; mostra il contenuto di un &lt;strong&gt;partial&lt;/strong&gt;. Serve a consentire la modifica dello stato pubblicato non pubblicato del commento restando nella visualizzazione elenco. La spiegazione nei dettagli è nell'articolo precedente perché il funzionamento è identico a quello della stessa colonna della gestione articoli: le azioni &lt;strong&gt;publish&lt;/strong&gt; ed &lt;strong&gt;unpublish&lt;/strong&gt;, collegate alle icone nel partial (&lt;em&gt;apps/backend/modules/comment/templates/_published.php&lt;/em&gt;), sono gestite come di regola dalla classe &lt;strong&gt;commentActions&lt;/strong&gt;, il metodo &lt;strong&gt;publish()&lt;/strong&gt; è implementato in &lt;strong&gt;LyraComment&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;Azioni batch&lt;/h3&gt;

&lt;p&gt;Anche per quanto riguarda questa parte posso tranquillamente rimandare all'articolo precedente visto che il funzionamento delle azioni batch &lt;strong&gt;publish&lt;/strong&gt; e &lt;strong&gt;unpublish&lt;/strong&gt; è identico a quello della gestione articoli.&lt;/p&gt;

&lt;h3&gt;Query creazione lista&lt;/h3&gt;

&lt;p&gt;Quando in una lista che mostra i record di una tabella il contenuto di una colonna viene ottenuto da una tabella correlata, come nel nostro caso il titolo dell'articolo dalla tabella articoli, conviene modificare la query utilizzata per l'estrazione dei record ed aggiungere una JOIN tra le tabelle. In questo modo si evita l'esecuzione di una query SQL distinta per ogni riga della lista.&lt;/p&gt;

&lt;p&gt;La chiave &lt;strong&gt;table_method&lt;/strong&gt; indica il nome del metodo che ritorna la query da eseguire e che viene implementato nel modello.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraCommentTable.class.php

class LyraCommentTable extends Doctrine_Table
{
  ...
  public function getBackendItemsQuery(Doctrine_Query $q)
  {
    $rootAlias = $q-&amp;gt;getRootAlias();
    $q-&amp;gt;leftJoin($rootAlias . '.CommentArticle a');

    return $q;
  }
}
&lt;/pre&gt;

&lt;h2&gt;Filtri commenti&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
    config:
      ...
      list:
      ...
      filter:
        display: [article_id, is_active]
      ...
&lt;/pre&gt;
&lt;p&gt;In &lt;strong&gt;filter&lt;/strong&gt; impostiamo i filtri di ricerca che ci consentono di visualizzare nell'elenco  i commenti pubblicati o non pubblicati (&lt;strong&gt;is_active&lt;/strong&gt;) e tutti i commenti di un determinato articolo (&lt;strong&gt;article_id&lt;/strong&gt;).&lt;/p&gt;

&lt;h2&gt;Modifica commento&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/comment/config/generator.yml

...
    config:
     ...
      list:
       ...
      filter:
       ...
      form:
        class: BackendLyraCommentForm
       ...
&lt;/pre&gt;
&lt;p&gt;Come si è visto la pubblicazione di un commento può essere fatta rimanendo nella visualizzazione elenco, se invece serve modificare il testo o uno degli altri campi si utilizza la funzione modifica standard.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;form&lt;/strong&gt; specifichiamo la &lt;strong&gt;classe&lt;/strong&gt; da utilizzare per il modulo di inserimento e modifica commenti del backend. La classe deriva da quella creata per il frontend a sua volta derivata dalla classe base generata da Doctrine.&lt;/p&gt;
&lt;pre&gt;
lib/form/doctrine/BackendLyraCommentForm.class.php

class BackendLyraCommentForm extends LyraCommentForm
{
  public function configure()
  {
      parent::configure();
      $this-&amp;gt;widgetSchema['is_active']-&amp;gt;setLabel('IS_ACTIVE');

      $this-&amp;gt;widgetSchema-&amp;gt;setHelp('author_email',false);
      $this-&amp;gt;widgetSchema-&amp;gt;moveField('is_active', sfWidgetFormSchema::FIRST);

  }
  protected function removeFields()
  {
      unset($this['created_at'], $this['updated_at']);
  }
}
&lt;/pre&gt;

&lt;p&gt;Nel metodo &lt;strong&gt;configure()&lt;/strong&gt;, prima richiamiamo la configurazione della classe base e poi impostiamo le configurazioni specifiche per il backend. Sovrascrivendo il metodo &lt;strong&gt;removeFields()&lt;/strong&gt; possiamo rendere visibili nel form di backend campi che erano stati resi invisibili nel form di frontend, in particolare il campo &lt;strong&gt;is_active&lt;/strong&gt; che contiene lo stato pubblicato / non pubblicato del commento.&lt;/p&gt;

&lt;p&gt;Resta da sistemare il link che porta dall'elenco articoli alla lista dei commenti inseriti su un articolo.&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s1600-h/lyra-articles.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 288px; height: 140px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s320/lyra-articles.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402890824320174034" /&gt;&lt;/a&gt;
&lt;pre&gt;
apps/backend/modules/article/templates/_comments.php

&amp;lt;?php
$total = $lyra_article-&amp;gt;countComments();
echo $total . ' / ' . ($total - $lyra_article-&amp;gt;countActiveComments());
?&amp;gt;
 (&amp;lt;?php echo link_to(__('LINK_SHOW_COMMENTS'),'@lyra_comment_comment?id=' . $lyra_article-&amp;gt;getId()); ?&amp;gt;)
&lt;/pre&gt;
&lt;p&gt;Si crea il link in base alla rotta che porta alla lista commenti (azione &lt;strong&gt;index&lt;/strong&gt;) aggiungendo un parametro &lt;em&gt;id&lt;/em&gt; (articolo). Poi nella classe actions del modulo commenti si legge il parametro per impostare automaticamente un filtro che visualizzi i commenti dell'articolo con quel determinato ID.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/comment/actions/actions.class.php

class commentActions extends autoCommentActions
{
  ...
  public function executeIndex(sfWebRequest $request)
  {
    if($request-&amp;gt;getParameter('id')) {
      $this-&amp;gt;setFilters(array('article_id'=&amp;gt;$request-&amp;gt;getParameter('id')));
    }
    parent::executeIndex($request);
    
  }
}
&lt;/pre&gt; 
&lt;p&gt;Impostato il filtro, si passa il controllo all'azione della classe base che è poi quella creata dal generatore del backend.&lt;/p&gt;

&lt;p&gt;Ho infine corretto un errore nella classe actions del modulo article del frontend: dopo la modifica delle rotte degli articoli (in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html"&gt;Generare URL SEF in symfony&lt;/a&gt;) il reindirizzamento alla pagina dell'articolo dopo aver inserito un commento non funzionava più. Tutto questo si trova nella &lt;strong&gt;revisione 20&lt;/strong&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-3181112920595286979?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/kx52P90i3uTIy3VjHWinKHw-vzQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/GOT_8l9U-6o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/3181112920595286979/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-commenti-backend.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3181112920595286979?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/3181112920595286979?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-gestione-commenti-backend.html" title="Lyra,  gestione commenti backend" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_unGpwtr1q-o/SvrslDfSR9I/AAAAAAAAAH4/ZI23hlHe-KE/s72-c/lyra-articles.png" height="72" width="72" /><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DEIGQn08eSp7ImA9WxNUF0o.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-423461604844258652</id><published>2009-11-09T15:20:00.000+01:00</published><updated>2009-11-09T15:22:03.371+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-09T15:22:03.371+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>symfony, personalizzare il backend</title><content type="html">&lt;p&gt;Arrivati a questo punto è necessario iniziare lo sviluppo delle funzionalità di backend di Lyra. Sarà anche l'occasione per una veloce panoramica sul &lt;strong&gt;generatore di backend&lt;/strong&gt; di symfony. Chi non ha grande familiarità con il framework può trovare molti maggiori dettagli nel tutorial ufficiale (&lt;a href="http://www.symfony-project.org/jobeet/1_2/Doctrine/it/12"&gt;Admin Generator&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Per prima cosa creiamo l'applicazione con il comando&lt;/p&gt;

&lt;pre&gt;
./symfony generate:app --escaping-strategy=on --csrf-secret=xgt67jhbv backend
&lt;/pre&gt;

&lt;p&gt;Si utilizzano le stesse opzioni viste per la generazione dell'applicazione frontend (in &lt;a href="http://sviluppare-in-rete.blogspot.com/2009/09/symfony-creazione-progetto-e.html"&gt;symfony, creazione progetto e applicazione&lt;/a&gt;): &lt;strong&gt;escaping-strategy&lt;/strong&gt; impostato a &lt;em&gt;on&lt;/em&gt; e &lt;strong&gt;csrf-secret&lt;/strong&gt; con una sequenza di caratteri scelti a caso.&lt;/p&gt;

&lt;p&gt;Eseguito il comando si noterà che è stata creata una cartella &lt;em&gt;apps/backend&lt;/em&gt; e nella cartella &lt;em&gt;web&lt;/em&gt; i front controller per l'applicazione backend:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;backend.php&lt;/li&gt;
&lt;li&gt;backend-dev.php&lt;/li&gt;&lt;/ul&gt;

&lt;h2&gt;Backend articoli&lt;/h2&gt;
 
&lt;p&gt;Symfony consente di generare automaticamente un'interfaccia di backend per ciascuna classe del modello. Iniziamo con la gestione articoli.&lt;/p&gt;

&lt;pre&gt;
./symfony doctrine:generate-admin backend LyraArticle --module=article
&lt;/pre&gt;

&lt;p&gt;A questo punto inserendo nel browser l'indirizzo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://lyra/backend_dev.php/article&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;siamo già in grado di visualizzare l'elenco degli articoli inseriti. Questo dando per scontato che si siano seguite le istruzioni degli articoli precedenti per creare un virtual host e il database del progetto e si siano eseguiti i comandi per la generazione del modello, delle tabelle e l'inserimento dei dati di esempio.&lt;/p&gt;

&lt;p&gt;Anche se l'interfaccia è funzionante e già consente di inserire, modificare e cancellare gli articoli, l'aspetto della pagina è piuttosto brutto da vedere perché non abbiamo personalizzato il &lt;strong&gt;layout&lt;/strong&gt; del backend e l'elenco degli articoli risulta troppo largo in quanto è stata creata una colonna per ciascun campo della tabella.&lt;/p&gt;

&lt;p&gt;La prima cosa da fare è la modifica del layout (&lt;em&gt;apps/backend/templates/layout.php&lt;/em&gt;) con l'aggiunta del relativo foglio di stile (&lt;em&gt;web/css/admin.css&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;Bisogna anche modificare questa riga di &lt;em&gt;apps/backend/config/view.yml&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
stylesheets:    [main.css]
&lt;/pre&gt;
&lt;p&gt;in questo modo&lt;/p&gt;
&lt;pre&gt;
stylesheets:    []
&lt;/pre&gt;
&lt;p&gt;Questo perché utilizziamo la funzione helper &lt;strong&gt;use_stylesheet()&lt;/strong&gt; per richiamare il foglio di stile &lt;em&gt;admin.css&lt;/em&gt; nel layout del backend.&lt;/p&gt;

&lt;p&gt;Il layout è ancora abbastanza grezzo, ma ritengo che in questa fase sia più importante mettere in piedi qualcosa che funzioni e pensare alle rifiniture in un secondo tempo. Per cui non riporto neppure il codice che dovrà necessariamente essere modificato parecchio nel corso dello sviluppo. Il contenuto dei due file può comunque essere visualizzato su Google Code (&lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/apps/backend/templates/layout.php"&gt;layout.php&lt;/a&gt;, &lt;a href="http://code.google.com/p/lyra-cms/source/browse/trunk/web/css/admin.css"&gt;admin.css&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Un primo livello di personalizzazione del backend si effettua modificando le impostazioni nel file di configurazione &lt;em&gt;generator.yml&lt;/em&gt; nella cartella &lt;em&gt;config&lt;/em&gt; di ogni modulo. La procedura è abbastanza standardizzata e ben documentata, mi limiterò ad elencare in estrema sintesi le operazioni svolte.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/config/generator.yml
...
  config:
      actions: ~
      fields:  ~
      list:
        title: TITLE_ARTICLES
        display: [=title,_comments,_published,_front_page,created_at,id]
        fields:
          title: {label: TH_TITLE}
          created_at: {label: TH_CREATED_AT, date_format: dd.MM.yy hh:mm}
          updated_at: {label: TH_UPDATED_AT, date_format: dd.MM.yy hh:mm}
          id: {label: TH_ID}
        ...
      filter:
        display: [title,is_active]
...
&lt;/pre&gt;
&lt;p&gt;Viene impostato un titolo per la pagina, vengono scelti i campi da mostrare nell'elenco in modo da ridurre il numero delle colonne e modificate le etichette delle intestazioni colonna. Il testo effettivo per titolo ed etichette come al solito sarà incluso nei file delle traduzioni. &lt;/p&gt;

&lt;p&gt;Vengono scelti i filtri di ricerca. Al momento si consente una ricerca per titolo articolo e stato pubblicazione.&lt;/p&gt;

&lt;p&gt;Da notare le colonne &lt;strong&gt;_comments&lt;/strong&gt;, &lt;strong&gt;_published&lt;/strong&gt;, &lt;strong&gt;_front_page&lt;/strong&gt; impostate nel parametro &lt;strong&gt;display&lt;/strong&gt;. il carattere '_'  iniziale ci dice che il contenuto di queste colonne non è il valore di un campo, ma il risultato della elaborazione di altrettanti &lt;strong&gt;partial&lt;/strong&gt; definiti in &lt;em&gt;apps/backend/modules/article/templates&lt;/em&gt;. Vediamoli uno per uno.&lt;/p&gt;

&lt;h2&gt;Contatori numero commenti articolo&lt;/h2&gt;

&lt;pre&gt;
apps/backend/modules/article/templates/_comments.php

&amp;lt;?php
$total = $lyra_article-&amp;gt;countComments();
echo $total . ' / ' . ($total - $lyra_article-&amp;gt;countActiveComments());
?&amp;gt;
 (&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;&amp;lt;?php echo __('LINK_SHOW_COMMENTS') ?&amp;gt;&amp;lt;/a&amp;gt;)
&lt;/pre&gt;
&lt;p&gt;Dovrebbe essere abbastanza comprensibile: nella colonna vengono visualizzati il numero totale dei commenti dell'articolo seguito dal numero dei commenti in attesa di moderazione e da un link che porterà all'elenco dei commenti dell'articolo. Il link non funzionerà fino a che non avremo sviluppato il modulo commenti nel backend. I metodi &lt;strong&gt;countComments()&lt;/strong&gt; e &lt;strong&gt;countActiveComments()&lt;/strong&gt; sono implementati nel modello (classe &lt;strong&gt;LyraArticle&lt;/strong&gt;) e utilizzano due semplici query per calcolare i valori dei contatori. &lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
  ...
  public function countActiveComments()
  {
    return $this-&amp;gt;getActiveCommentsQuery()
      -&amp;gt;count();
  }
  public function countComments()
  {
    return $this-&amp;gt;getCommentsQuery()
      -&amp;gt;count();
  }
  ...
  protected function getCommentsQuery()
  {
    $q = Doctrine_Query::create()
      -&amp;gt; from('LyraComment c')
      -&amp;gt;andWhere('c.article_id = ?', $this-&amp;gt;getId());
    return $q;
  }
  protected function getActiveCommentsQuery()
  {
    $q = Doctrine::getTable('LyraComment')
      -&amp;gt;getActiveItemsQuery();

    $q-&amp;gt;andWhere($q-&amp;gt;getRootAlias() .'.article_id = ?', $this-&amp;gt;getId());
      
    return $q;
  }
} //fine LyraArticle
&lt;/pre&gt;

&lt;h2&gt;Stato pubblicato / non pubblicato&lt;/h2&gt;

&lt;p&gt;Nel backend standard di symfony nelle colonne relative ai campi &lt;strong&gt;boolean&lt;/strong&gt; viene mostrato un segno di spunta se il campo è &lt;em&gt;true&lt;/em&gt;, se è &lt;em&gt;false&lt;/em&gt; la cella viene lasciata vuota. Per modificare il valore del campo da vero a falso e viceversa si deve entrare in modifica del record. Sono abituato al backend di Joomla dove in questi casi il cambio di stato (ad esempio pubblicato / non pubblicato) si può effettuare rimanendo in visualizzazione elenco semplicemente facendo click sull'icona nella cella e ho voluto mantenere questa scorciatoia. La cosa si può fare abbastanza semplicemente mostrando un partial (&lt;strong&gt;_published&lt;/strong&gt;) al posto del valore del campo.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/templates/_published.php

&amp;lt;?php if ($lyra_article-&amp;gt;getIsActive()): ?&amp;gt;
  &amp;lt;?php echo link_to(image_tag('backend/yes.png', array('alt' =&amp;gt; __('LINK_T_PUBLISHED'))),'article/unpublish?id='.$lyra_article-&amp;gt;getId(), array('title' =&amp;gt; __('LINK_T_PUBLISHED'))) ?&amp;gt;
&amp;lt;?php else: ?&amp;gt;
  &amp;lt;?php echo link_to(image_tag('backend/no.png', array('alt' =&amp;gt; __('LINK_T_UNPUBLISHED', array(), 'sf_admin'))),'article/publish?id='.$lyra_article-&amp;gt;getId(), array('title' =&amp;gt; __('LINK_T_UNPUBLISHED'))) ?&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Se l'articolo è pubblicato si mostra l'icona del segno di spunta collegata all'azione per de-pubblicare ) l'articolo; l'inverso se l'articolo non è pubblicato.&lt;/p&gt;

&lt;p&gt;Aggiungiamo la gestione delle azioni &lt;strong&gt;publish&lt;/strong&gt; / &lt;strong&gt;unpublish&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;
apps/backend/modules/article/actions/actions.class.php
..
class articleActions extends autoArticleActions
{
  public function executePublish(sfwebRequest $request)
  {
    $this-&amp;gt;lyra_article = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;lyra_article-&amp;gt;publish();
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_PUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  public function executeUnpublish(sfwebRequest $request)
  {
    $this-&amp;gt;lyra_article = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;lyra_article-&amp;gt;publish(false);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_UNPUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  ...
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Il metodo publish() va implementato nel modello&lt;/p&gt;
&lt;pre&gt;
lib/model/doctrine/LyraArticle.class.php

class LyraArticle extends BaseLyraArticle
{
  ...
  public function publish($on = true)
  {
    $this-&amp;gt;setIsActive($on);
    $this-&amp;gt;save();
  }
  ...
} //fine LyraArticle
&lt;/pre&gt;
&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgYWRbfV7I/AAAAAAAAAHo/Uxqkrr28RLE/s1600-h/lyra-backend2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 101px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgYWRbfV7I/AAAAAAAAAHo/Uxqkrr28RLE/s320/lyra-backend2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402094523945801650" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Stato in prima pagina / non in prima pagina&lt;/h2&gt;

&lt;p&gt;La stessa cosa viene fatta per lo stato di pubblicazione in prima pagina. Non riporto il codice che è praticamente identico: il partial è &lt;strong&gt;_front_page&lt;/strong&gt; e le azioni &lt;strong&gt;feature&lt;/strong&gt; e &lt;strong&gt;unfeature&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Azioni batch&lt;/h2&gt;

&lt;p&gt;Tramite &lt;strong&gt;azioni batch&lt;/strong&gt; si può compiere una determinata operazione 'in serie' su un gruppo di record selezionati nell'elenco. Il generatore di backend di symfony crea automaticamente un'azione di questo tipo che consente la cancellazione di record multipli.&lt;/p&gt;

&lt;p&gt;Ne creeremo altre due personalizzate per impostare / rimuovere lo stato di pubblicato su più articoli. Per prima cosa definiamo le azioni batch nel file di configurazione.&lt;/p&gt;

&lt;pre&gt;
apps/backend/modules/article/config/generator.yml
...
    config:
      actions: ~
      fields:  ~
      list:
        #parte già esaminata sopra
        batch_actions:
          _delete: ~
          publish: {label: PUBLISH}
          unpublish: {label: UNPUBLISH}
      ...
&lt;/pre&gt;
&lt;p&gt;Scriviamo poi i gestori delle azioni nella classe actions del modulo article.&lt;/p&gt;

&lt;pre&gt;
class articleActions extends autoArticleActions
{
 
  public function executeBatchPublish(sfWebRequest $request)
  {
    $ids = $request-&amp;gt;getParameter('ids');
    Doctrine::getTable('LyraArticle')-&amp;gt;publish($ids);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_PUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
  public function executeBatchUnpublish(sfWebRequest $request)
  {
    $ids = $request-&amp;gt;getParameter('ids');
    Doctrine::getTable('LyraArticle')-&amp;gt;publish($ids, false);
    $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'MSG_ARTICLE_UNPUBLISHED');
    $this-&amp;gt;redirect('@lyra_article_article');
  }
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;I nomi dei metodi che processano un'azione batch devono seguire uno standard: executeBatch + nome azione. Il parametro &lt;em&gt;ids&lt;/em&gt; della richiesta contiene un array con gli &lt;strong&gt;ID&lt;/strong&gt; dei record selezionati tramite le caselle di selezione nell'elenco. &lt;/p&gt;

&lt;p&gt;Implementiamo il metodo &lt;strong&gt;publish()&lt;/strong&gt; in &lt;strong&gt;LyraArticleTable&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;
class LyraArticleTable extends Doctrine_Table
{
  public function publish($ids, $on = true)
  {
    $q = $this-&amp;gt;createQuery('a')
      -&amp;gt;whereIn('a.id', $ids);

    foreach ($q-&amp;gt;execute() as $item) {
      $item-&amp;gt;publish($on);
    }
  }
} //fine LyraArticleTable
&lt;/pre&gt;
&lt;p&gt;Viene fatta una query per selezionare i record in base al valore degli ID passati dall'azione. Per ogni record si invoca il metodo &lt;strong&gt;publish()&lt;/strong&gt; di &lt;strong&gt;LyraArticle&lt;/strong&gt; che abbiamo creato poco sopra.&lt;/p&gt;
&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgZL2600xI/AAAAAAAAAHw/GoQ5T99BxT0/s1600-h/lyra-backend3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 279px; height: 245px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgZL2600xI/AAAAAAAAAHw/GoQ5T99BxT0/s320/lyra-backend3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402095444542411538" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Tema di backend personalizzato&lt;/h2&gt;

&lt;p&gt;Per ottenere un livello di personalizzazione maggiore di quelo che si ottiene attraverso le impostazioni del file di configurazione, ci si può creare un proprio tema per il backend da utilizzare al posto di quello predefinito.&lt;/p&gt;

&lt;p&gt;La procedura è spiegata nella documentazione ufficiale (&lt;a href="http://www.symfony-project.org/book/1_2/14-Generators"&gt;Admin Generator&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;La cosa più semplice da fare è partire dal tema per il backend predefinito che si trova in&lt;/p&gt;

&lt;pre&gt;
lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin
&lt;/pre&gt;

&lt;p&gt;Copiare il contenuto della cartella &lt;em&gt;admin&lt;/em&gt; (cioè le cartelle &lt;em&gt;parts&lt;/em&gt;, &lt;em&gt;skeleton&lt;/em&gt; e &lt;em&gt;template&lt;/em&gt;) in &lt;em&gt;data/generator/sfDoctrineModule/admin&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Possiamo a questo punto personalizzare il tema in questa cartella senza rischiare di perdere le nostre modifiche al successivo aggiornamento del framework. Al momento le personalizzazioni sono minime, ho soltanto creato uno slot per visualizzare il titolo delle pagine in una posizione diversa.&lt;/p&gt;

&lt;p&gt;Da così&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgXXOmDsyI/AAAAAAAAAHY/40VfeDX-eIg/s1600-h/lyra-backend.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 130px;" src="http://2.bp.blogspot.com/_unGpwtr1q-o/SvgXXOmDsyI/AAAAAAAAAHY/40VfeDX-eIg/s320/lyra-backend.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402093440853062434" /&gt;&lt;/a&gt;
&lt;p&gt;a così&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgXfmUmrpI/AAAAAAAAAHg/BDg-L4baI9w/s1600-h/lyra-backend1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 102px;" src="http://3.bp.blogspot.com/_unGpwtr1q-o/SvgXfmUmrpI/AAAAAAAAAHg/BDg-L4baI9w/s320/lyra-backend1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5402093584661261970" /&gt;&lt;/a&gt;
&lt;pre&gt;
data/generator/sfDoctrineModule/admin/template/templates/indexSuccess.php
...
[?php slot('page_title',&amp;lt;?php echo $this-&amp;gt;getI18NString('list.title') ?&amp;gt;) ?]
&amp;lt;div id=&amp;quot;sf_admin_container&amp;quot;&amp;gt;
//La riga seguente viene rimossa
&lt;s&gt;&amp;lt;h1&amp;gt;[?php echo &amp;lt;?php echo $this-&amp;gt;getI18NString('list.title') ?&amp;gt; ?]&amp;lt;/h1&amp;gt;&lt;/s&gt;
...
&lt;/pre&gt; 

Lo slot è richiamato in questa parte del layout

&lt;pre&gt;
apps/backend/templates/layout.php
...
      &amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
        &amp;lt;h1&amp;gt;
          &amp;lt;?php include_slot('page_title') ?&amp;gt;
        &amp;lt;/h1&amp;gt;
      &amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;Modifiche del tutto analoghe sono state fatte nei file &lt;strong&gt;editSuccess.php&lt;/strong&gt; e &lt;strong&gt;newSucces.php&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le personalizzazioni apportate al tema in questo modo saranno applicate anche ai moduli creati successivamente con il comando  &lt;strong&gt;doctrine:generate-admin&lt;/strong&gt;. Lo vedremo la prossima volta quando sarà creato il modulo per la gestione dei commenti da backend.&lt;/p&gt;

&lt;p&gt;Il codice della parte discussa fino a questo momento è come al solito su Google Code (&lt;strong&gt;revisione 19&lt;/strong&gt;).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-423461604844258652?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/r5T4tglXc3YiM7JNXjOnL0oriW0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/1ZhdyGFYaM4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/423461604844258652/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/symfony-personalizzare-il-backend.html#comment-form" title="4 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:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></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>4</thr:total></entry><entry gd:etag="W/&quot;DEcGRXo9eip7ImA9WxNUFEw.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-8144363429249550121</id><published>2009-11-05T11:12:00.000+01:00</published><updated>2009-11-05T11:13:44.462+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-05T11:13:44.462+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Generare URL SEF in symfony</title><content type="html">&lt;p&gt;Quando si sono delineate le funzionalità essenziali di Lyra, nell'elenco era presente la creazione di URL semplificate, 'pulite' o SEF (ognuno utilizzi la terminologia che preferisce). Fino a questo momento abbiamo incontrato tre tipi di URL.&lt;/p&gt;

&lt;p&gt;1) Prima pagina e questa ovviamente non pone problemi particolari.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2) Vista articolo a tutta pagina. Esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/show/id/2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3) Lista articoli per etichetta. Esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/label/id/1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Queste URL non sono troppo lunghe e non presentano una lista di parametri sotto forma di query string, sono sicuramente migliori di, ad esempio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/index.php?module=article&amp;action=show&amp;id=2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Però credo che molti utenti  avrebbero come minimo qualche dubbio a definirle vere e proprie URL SEF. Va detto che hanno il vantaggio di poter essere generate in base a &lt;strong&gt;rotte predefinite&lt;/strong&gt; senza che sia necessario modificare alcun file di configurazione, se si è disposti a rinunciare a questo vantaggio si possono personalizzare le URL della propria applicazione come si preferisce.&lt;/p&gt;

&lt;p&gt;Come esempio modificheremo la URL della vista articolo a tutta pagina in modo che risulti così:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/titolo-articolo.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Per prima cosa definiamo una nuova rotta. L'ordine delle definizioni è importante per cui inseriamo la nostra prima di quelle già presenti (rotte di default)&lt;/p&gt;

&lt;pre&gt;
apps/frontend/config/routing.yml

article_show:
  url: /article/:slug.html
  class: sfDoctrineRoute
  options:
    type: object
    model: LyraArticle
    method: findItem
  param:
    module: article
    action: show
  requirements:
    sf_method: [get]
&lt;/pre&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;url&lt;/strong&gt; indica il formato della URL generata dalla rotta: gli identificatori preceduti da ':' (es. &lt;strong&gt;:slug&lt;/strong&gt;) sono variabili;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;class&lt;/strong&gt; è la classe della rotta: &lt;strong&gt;sfDoctrineRoute&lt;/strong&gt; indica che questa rotta è associata ad un oggetto (record) o insieme di oggetti  (collezione di record) istanze di una classe modello Doctrine;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;options&lt;/strong&gt; indica che la rotta è associata ad un singolo record (&lt;strong&gt;type&lt;/strong&gt; è &lt;em&gt;object&lt;/em&gt;, l'alternativa, &lt;em&gt;list&lt;/em&gt; indicherebbe l'associazione con una collezione di record) di classe &lt;strong&gt;LyraArticle&lt;/strong&gt;. &lt;strong&gt;findItem&lt;/strong&gt; è il metodo del modello che servirà a reperire l'oggetto corrispondente alla rotta, nel nostro caso il record articolo da visualizzare;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;param&lt;/strong&gt; indica che la richiesta che corrisponde a questa rotta verrà processata dall'azione &lt;strong&gt;show&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt;;&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;requirements&lt;/strong&gt; indica che il metodo della richiesta deve essere &lt;strong&gt;GET&lt;/strong&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Bisogna poi modificare i template in modo che i link agli articoli siano generati in base alla rotta appena definita.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/_list.php

...
  &amp;lt;h2 class=&amp;quot;article-title&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to($item-&amp;gt;getTitle(), '@article_show?slug=' . $item-&amp;gt;getSlug())?&amp;gt;
  &amp;lt;/h2&amp;gt;
...
  &amp;lt;span class=&amp;quot;article-readmore&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to(__('LINK_READMORE'), '@article_show?slug=' . $item-&amp;gt;getSlug(), array('title'=&amp;gt;$item-&amp;gt;getTitle()))?&amp;gt;
  &amp;lt;/span&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;Alla funzione helper &lt;strong&gt;link_to()&lt;/strong&gt; passiamo direttamente il nome della rotta preceduto da '@' a cui vengono accodati i valori per le parti variabili della rotta (&lt;strong&gt;:slug&lt;/strong&gt;) nella stessa forma che sarebbe utilizzata per costruire una query string. Chiaramente il link risultante viene generato come indicato dal parametro &lt;strong&gt;url&lt;/strong&gt; nella definizione della rotta, cioè in un formato a 'segmenti' senza alcun parametro in forma di query string.&lt;/p&gt;

&lt;p&gt;Resta da vedere come viene individuato l'articolo da visualizzare quando viene cliccato uno dei link generati nel modo appena visto. Abbiamo detto che una rotta è associata ad un oggetto (o ad una collezione di oggetti), quindi partendo dalla rotta possiamo risalire all'oggetto corrispondente. Questo avviene nell'azione (le righe sbarrate sono quelle presenti nella versione precedente e ora rimosse)&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {
    &lt;s&gt;$this-&amp;gt;item = Doctrine::getTable('LyraArticle')&lt;/s&gt;
    &lt;s&gt;  -&amp;gt;find($request-&amp;gt;getParameter('id'));&lt;/s&gt;
    &lt;s&gt;$this-&amp;gt;forward404Unless($this-&amp;gt;item);&lt;/s&gt;
    $this-&amp;gt;item = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    ...
   }
 ...  
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Il metodo &lt;strong&gt;getObject()&lt;/strong&gt; invoca il metodo definito nelle &lt;strong&gt;options&lt;/strong&gt; della rotta (&lt;strong&gt;findItem()&lt;/strong&gt;) e gli passa un array di parametri contenente le variabili della rotta. Nel metodo dobbiamo eseguire una query in base a questi parametri e ritornare l'oggetto oppure &lt;em&gt;false&lt;/em&gt;: nel primo caso l'oggetto viene passato indietro all'azione ed il flusso continua; nel secondo caso nel codice di &lt;strong&gt;getObject()&lt;/strong&gt; viene sollevata un'eccezione che genera un errore 404.&lt;/p&gt;

&lt;p&gt;Proprio perché nel caso di oggetto non trovato l'errore viene gestito internamente abbiamo rimosso dall'azione la riga&lt;/p&gt;

&lt;pre&gt;
$this-&gt;forward404Unless($this-&gt;item);
&lt;/pre&gt;

&lt;p&gt;Se per qualsiasi ragione vogliamo continuare a gestire questa situazione di errore nel codice dell'azione, basta modificare la definizione della rotta in questo modo&lt;/p&gt;

&lt;pre&gt;
article_show:
  ...
  options:
    model: LyraArticle
    type: object
    method: findItem
    allow_empty: true
...
&lt;/pre&gt;
&lt;p&gt;Con l'opzione &lt;strong&gt;allow_empty&lt;/strong&gt; impostata a &lt;em&gt;true&lt;/em&gt; il metodo &lt;strong&gt;getObject()&lt;/strong&gt; ritorna &lt;em&gt;null&lt;/em&gt; in caso di oggetto non trovato senza generare internamente nessuna eccezione. Chiaramente a questo punto dovremmo reintrodurre la chiamata al metodo &lt;strong&gt;forward404Unless()&lt;/strong&gt; o comunque gestire la situazione opportunamente. Il codice ad esempio dovrebbe essere modificato così:&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {    
    $this-&amp;gt;item = $this-&amp;gt;getRoute()-&amp;gt;getObject();
    $this-&amp;gt;forward404Unless($this-&amp;gt;item);
    ...
   }
 ...  
} // fine articleActions
&lt;/pre&gt;
&lt;p&gt;Resta solo da implementare &lt;strong&gt;findItem()&lt;/strong&gt; nel modello.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraArticleTable.class.php

class LyraArticleTable extends Doctrine_Table
{
...
  public function findItem($params = array())
  {
    if(!isset($params['slug'])) {
      return false;
    }
    $q = $this-&amp;gt;getActiveItemsQuery();
    $q-&amp;gt;andWhere($q-&amp;gt;getRootAlias() .'.slug = ?', $params['slug']);
    
    return $q-&amp;gt;fetchOne();
  }
} // fine LyraArticleTable
&lt;/pre&gt;
&lt;p&gt;Nel nostro esempio l'array di parametri contiene un solo elemento, il valore del campo &lt;strong&gt;slug&lt;/strong&gt; dell'articolo.&lt;/p&gt;

&lt;p&gt;Ho inoltre aggiunto una rotta (&lt;strong&gt;article_label&lt;/strong&gt;) per dare questo formato alle URL della pagina con l'elenco degli articoli per etichetta generate dal componente visto nei due articoli precedenti:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/label/slug-etichetta&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Il procedimento è del tutto analogo a quello appena visto, il codice è disponibile nel repository (&lt;strong&gt;revisione 18&lt;/strong&gt;) insieme a tutto quello illustrato sopra.&lt;/p&gt;

&lt;p&gt;Naturalmente le possibilità offerte da symfony non si fermano certo qui. Si potrebbe includere la data dell'articolo nella URL per creare un &lt;em&gt;permalink&lt;/em&gt; in stile blog&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/2009/11/titolo-articolo.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Oppure includere le etichette/categorie&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://www.example.com/article/javascript/jquery/articolo-su-jquery.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ma è prematuro continuare lo sviluppo di questa parte adesso. Il formato delle URL dei contenuti è un aspetto importante di un cms e richiederà ulteriore lavoro. Al momento va data la precedenza ad alcune parti essenziali dell'applicazione che mancano completamente, ad esempio la gestione del backend di cui non abbiamo scritto neppure una riga. Si inizierà dalla prossima volta.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-8144363429249550121?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/0B5UOqWdC0sDig7ZpeoAZbpjq-g/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/gLpUwE3RPBQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/8144363429249550121/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8144363429249550121?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/8144363429249550121?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/generare-url-sef-in-symfony.html" title="Generare URL SEF in symfony" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;C0QHRng6cCp7ImA9WxNUEks.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-5445246836473799670</id><published>2009-11-03T16:13:00.000+01:00</published><updated>2009-11-03T16:15:37.618+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-03T16:15:37.618+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, elenco articoli per etichetta</title><content type="html">&lt;p&gt;Come si è visto nell'articolo precedente i link del menù &lt;em&gt;Etichette&lt;/em&gt; hanno questo formato&lt;/p&gt;

&lt;p&gt;&lt;code&gt;article/label/id/x&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Dove x è l'ID del record etichetta.&lt;/p&gt;

&lt;p&gt;Dobbiamo scrivere il codice per l'azione &lt;strong&gt;label&lt;/strong&gt; del modulo &lt;strong&gt;article&lt;/strong&gt; ed il relativo template che determinerà l'aspetto della pagina con l'elenco degli articoli catalogati sotto una certa etichetta. L'elenco può naturalmente essere anche lungo quindi avremo bisogno di una funzione di paginazione, symfony ci mette a disposizione una classe dedicata a questo scopo.&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
...
  public function executeLabel(sfWebRequest $request)
  {
    $this-&amp;gt;forward404Unless(
      $this-&amp;gt;label = Doctrine::getTable('LyraLabel')
        -&amp;gt;find($request-&amp;gt;getParameter('id'))
    );
    $this-&amp;gt;pager = new sfDoctrinePager('LyraLabel', 25);
    $this-&amp;gt;pager-&amp;gt;setQuery($this-&amp;gt;label-&amp;gt;getItemsQuery());
    $this-&amp;gt;pager-&amp;gt;setPage($request-&amp;gt;getParameter('page', 1));
    $this-&amp;gt;pager-&amp;gt;init();
  }
...
}
&lt;/pre&gt;
All'oggetto &lt;strong&gt;pager&lt;/strong&gt; viene passata la query per la selezione degli articoli. Si imposta un valore costante (25) per il numero di record per pagina, in seguito questo parametro dovrà essere gestito dalla configurazione. La creazione della query avviene nel modello.

&lt;pre&gt;
lib/model/doctrine/LyraLabel.class.php

class LyraLabel extends BaseLyraLabel
{
  public function getItemsQuery() {
    $q = Doctrine::getTable('LyraArticle')
      -&amp;gt;getActiveItemsQuery();

    $q-&amp;gt;innerJoin($q-&amp;gt;getRootAlias().'.ArticleLabels l')
      -&amp;gt;andWhere('l.id = ?', $this-&amp;gt;getId());
    return $q;
  }
  ...
}
&lt;/pre&gt;
&lt;p&gt;Interessante notare la sintassi &lt;strong&gt;DQL&lt;/strong&gt; (Doctrine Query Language) usata per la query. Le tabelle articoli (&lt;em&gt;articles&lt;/em&gt;, modello &lt;strong&gt;LyraArticle&lt;/strong&gt;) ed etichette (&lt;em&gt;labels&lt;/em&gt;, modello &lt;strong&gt;LyraLabel&lt;/strong&gt;) sono tra loro in una relazione molti a molti tramite una tabella intermedia (&lt;em&gt;article_label&lt;/em&gt;, modello &lt;strong&gt;LyraArticleLabel&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;La relazione è definita in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt; (vedere relations di &lt;strong&gt;LyraArticle&lt;/strong&gt;) ed identificata come &lt;strong&gt;ArticleLabels&lt;/strong&gt;: questo identificatore è l'unica informazione che passiamo al metodo &lt;strong&gt;innerJoin()&lt;/strong&gt;, come si vede non ci sono clausole ON né nomi di tabelle in quanto tutte le informazioni necessarie a Doctrine per creare la query si trovano nello schema.&lt;/p&gt;

&lt;p&gt;Veniamo al template &lt;em&gt;labelSuccess.php&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;
apps/frontend/modules/article/templates/labelSuccess.php

&amp;lt;?php 
slot('page_title', $label-&amp;gt;title);
...
&lt;/pre&gt;

&lt;p&gt;Impostiamo uno &lt;strong&gt;slot&lt;/strong&gt; per visualizzare il titolo della pagina. Con gli slot si può inserire del contenuto in zone predeterminate del layout.&lt;/p&gt;

&lt;pre&gt;
...
include_partial('article/list', array('items'=&amp;gt;$pager-&amp;gt;getResults()));
?&amp;gt;
&amp;lt;?php if ($pager-&amp;gt;haveToPaginate()): ?&amp;gt;
  &amp;lt;?php 
    $base = 'article/label?id=' . $label-&amp;gt;getId() . '&amp;amp;page=';
  ?&amp;gt;
  &amp;lt;div class=&amp;quot;pagination&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to('First', $base . '1');?&amp;gt;
    &amp;lt;?php echo link_to('Prev', $base . $pager-&amp;gt;getPreviousPage());?&amp;gt;
    &amp;lt;?php foreach ($pager-&amp;gt;getLinks() as $page): ?&amp;gt;
      &amp;lt;?php if ($page == $pager-&amp;gt;getPage()): ?&amp;gt;
        &amp;lt;?php echo $page ?&amp;gt;
      &amp;lt;?php else:
        echo link_to($page, $base . $page);
      endif; ?&amp;gt;
    &amp;lt;?php endforeach; ?&amp;gt;
    &amp;lt;?php echo link_to('Next', $base . $pager-&amp;gt;getNextPage());?&amp;gt;
    &amp;lt;?php echo link_to('Last', $base . $pager-&amp;gt;getLastPage());?&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;?php endif; ?&amp;gt;
&amp;lt;!-- Fine  labelSuccess.php --&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Per la visualizzazione dell'elenco articoli riutilizziamo il partial creato per la prima pagina: di ogni articolo sarà visualizzato il titolo, la data e il sommario con link 'leggi tutto'. Il metodo &lt;strong&gt;getResults()&lt;/strong&gt; dell'oggetto pager restituisce gli articoli da visualizzare in base al numero di pagina che abbiamo impostato nell'azione. La parte restante del template serve alla visualizzazione dei link per la paginazione.&lt;/p&gt;

&lt;p&gt;Resta da aggiungere al layout l'istruzione per includere il contenuto dello slot impostato nel template.&lt;/p&gt;

&lt;p&gt;Questa parte&lt;/p&gt;
&lt;pre&gt;
apps/frontend/templates/layout.php
...
&amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;Titolo pagina&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;va modificata in questo modo&lt;/p&gt;
&lt;pre&gt;
apps/frontend/templates/layout.php
...
&amp;lt;div id=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;&amp;lt;?php include_slot('page_title'); ?&amp;gt;&amp;lt;/h3&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;
&lt;p&gt;Tutto questo si trova nella &lt;strong&gt;revisione 17&lt;/strong&gt; su &lt;a href="http://code.google.com/p/lyra-cms/"&gt;Google Code&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-5445246836473799670?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34FAiU-CYyG2U6cQVqw1yl80tKk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/mPj5AkQRMCo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/5445246836473799670/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-elenco-articoli-per-etichetta.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5445246836473799670?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/5445246836473799670?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-elenco-articoli-per-etichetta.html" title="Lyra, elenco articoli per etichetta" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkQFR3c9eyp7ImA9WxNUEUo.&quot;"><id>tag:blogger.com,1999:blog-7229364186577699007.post-4162375033990089189</id><published>2009-11-02T14:57:00.000+01:00</published><updated>2009-11-02T14:58:36.963+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-02T14:58:36.963+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="symfony" /><title>Lyra, componente etichette</title><content type="html">&lt;p&gt;Sui blog o altri siti che pubblicano notizie e articoli è abbastanza comune trovare un menù con una lista di collegamenti a pagine che mostrano l'elenco degli articoli appartenenti ad una determinata categoria. Un esempio è data dal riquadro &lt;em&gt;Etichette&lt;/em&gt; presente nella colonna destra delle pagine di questo blog. Vediamo come implementare questa funzione in Lyra.&lt;/p&gt;

&lt;p&gt;Quando si devono visualizzare informazioni in una colonna laterale o comunque in un'area diversa dal corpo principale della pagina si può utilizzare un &lt;strong&gt;componente&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vediamo innanzi tutto come il componente viene richiamato dal layout:&lt;/p&gt;

&lt;pre&gt;
apps/frontend/templates/layout.php

...
&amp;lt;div id=&amp;quot;rightbar&amp;quot;&amp;gt;
  &amp;lt;?php include_component('article', 'labels', array('catalog'=&amp;gt;'Argomento')) ?&amp;gt;
&amp;lt;/div&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;Gli argomenti passati alla funzione helper &lt;strong&gt;include_component()&lt;/strong&gt; sono nell'ordine: il nome del modulo, il nome del componente, un array opzionale contenente parametri che saranno accessibili dal codice del componente (vedremo subito come).&lt;/p&gt;

&lt;p&gt;In symfony un componente consiste in una action ed un template.&lt;/p&gt;

&lt;h2&gt;Action di un componente&lt;/h2&gt;

&lt;p&gt;Viene implementata come metodo &lt;strong&gt;[componente]Execute()&lt;/strong&gt; di una classe &lt;strong&gt;[modulo]Components&lt;/strong&gt; derivata da &lt;strong&gt;sfComponents&lt;/strong&gt; e definita in &lt;em&gt;apps/frontend/modules/[modulo]/actions/components.class.php&lt;/em&gt;. Nel nostro caso (componente &lt;strong&gt;labels&lt;/strong&gt;, modulo &lt;strong&gt;article&lt;/strong&gt;) quindi&lt;/p&gt;

&lt;pre&gt;
apps/frontend/modules/article/actions/components.class.php

class articleComponents extends sfComponents
{
  public function executeLabels(sfWebRequest $request)
  {
    $catalog = Doctrine::getTable('LyraCatalog')
      -&amp;gt;findOneByName($this-&amp;gt;catalog);
    $this-&amp;gt;tree = $catalog-&amp;gt;getLabelTree();
  }
}
&lt;/pre&gt;
 
&lt;p&gt;&lt;strong&gt;$this-&gt;catalog&lt;/strong&gt; contiene il valore del parametro &lt;em&gt;catalog&lt;/em&gt; contenuto nell'array di parametri passato ad &lt;strong&gt;include_component()&lt;/strong&gt;. Questo valore viene passato a &lt;strong&gt;findOneByName()&lt;/strong&gt; per ottenere l'oggetto record corrispondente, poi con &lt;strong&gt;getLabelTree()&lt;/strong&gt; otteniamo l'albero delle etichette associate al catalogo. Questo metodo va ovviamente implementato nella classe &lt;strong&gt;LyraCatalog&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
lib/model/doctrine/LyraCatalog.class.php

class LyraCatalog extends BaseLyraCatalog
{
  public function getLabelTree()
  {
    $q = Doctrine_Query::create()
      -&amp;gt;from('LyraLabel l')
      -&amp;gt;where('l.catalog_id = ? and l.level = 0', $this-&amp;gt;getId());

    $root = $q-&amp;gt;fetchOne();

    return $root-&amp;gt;getNode()-&amp;gt;getDescendants();
  }
}
&lt;/pre&gt;
&lt;p&gt;Per capire il codice va  tenuto presente che esistono relazioni gerarchiche tra i record della tabella Etichette. I record a livello zero (ne esiste uno per ogni catalogo) sono le radici dell'albero delle categorie associate ad un catalogo. Con una query selezioniamo il record radice del catalogo (&lt;strong&gt;$root&lt;/strong&gt;): i suoi discendenti sono l'albero delle categorie che ci interessa e che possiamo ottenere semplicemente con la chiamata ad un singolo metodo &lt;strong&gt;getDescendants()&lt;/strong&gt; perché la tabella Etichette è costruita come &lt;strong&gt;nested set&lt;/strong&gt; (vedere &lt;strong&gt;LyraLabel&lt;/strong&gt; in &lt;em&gt;config/doctrine/schema.yml&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;Template di un componente&lt;/h2&gt;

&lt;p&gt;Il template di un componente non è diverso da quelli già visti per le singole azioni di un modulo.&lt;/p&gt;

&lt;pre&gt;
&amp;lt;h4&amp;gt;&amp;lt;?php echo __('HEAD_LABELS')?&amp;gt;&amp;lt;/h4&amp;gt;
&amp;lt;ul class=&amp;quot;label-list&amp;quot;&amp;gt;
&amp;lt;?php foreach($tree as $node): ?&amp;gt;
  &amp;lt;li class=&amp;quot;lev&amp;lt;?php echo $node-&amp;gt;getLevel(); ?&amp;gt;&amp;quot;&amp;gt;
    &amp;lt;?php echo link_to($node-&amp;gt;getName(),'article/label?id='.$node-&amp;gt;getId()); ?&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;?php endforeach ?&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Tutto dovrebbe essere abbastanza chiaro. Faccio notare solo che:&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;$tree&lt;/strong&gt; nel template contiene il valore della proprietà assegnata nell'azione con &lt;strong&gt;$this-&gt;tree&lt;/strong&gt; con il meccanismo già visto in precedenza;&lt;/li&gt;

&lt;li&gt;sarà necessario creare una nuova azione (&lt;strong&gt;label&lt;/strong&gt;) nel modulo &lt;strong&gt;article&lt;/strong&gt; che servirà a visualizzare la pagina con l'elenco degli articoli appartenenti alla categoria. La funzione helper &lt;strong&gt;link_to()&lt;/strong&gt; genera un link per questa azione.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_unGpwtr1q-o/SutggyKg1qI/AAAAAAAAAHQ/l30f-09-mv4/s1600-h/lyra-menu.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 269px; height: 131px;" src="http://1.bp.blogspot.com/_unGpwtr1q-o/SutggyKg1qI/AAAAAAAAAHQ/l30f-09-mv4/s320/lyra-menu.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5398514694671619746" /&gt;&lt;/a&gt;
&lt;p&gt;L'implementazione dell'azione &lt;strong&gt;label&lt;/strong&gt; sarà fatta la prossima volta. Per il momento quanto sviluppato fino a questo punto si trova nella &lt;strong&gt;revisione 16&lt;/strong&gt; su Google Code.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7229364186577699007-4162375033990089189?l=sviluppare-in-rete.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/_oJmRUyxmcDAg1suutm8Dj1i_gc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/sviluppare-in-rete/~4/cEfB7zlBwQ0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sviluppare-in-rete.blogspot.com/feeds/4162375033990089189/comments/default" title="Commenti sul post" /><link rel="replies" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-componente-etichette.html#comment-form" title="0 Commenti" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4162375033990089189?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7229364186577699007/posts/default/4162375033990089189?v=2" /><link rel="alternate" type="text/html" href="http://sviluppare-in-rete.blogspot.com/2009/11/lyra-componente-etichette.html" title="Lyra, componente etichette" /><author><name>Massimo</name><uri>http://www.blogger.com/profile/02775448460263489533</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></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>0</thr:total></entry></feed>

