venerdì 25 marzo 2011

Utilizzare fixtures in Symfony2, Doctrine2

Articolo modificato il 1 Maggio 2011 per far funzionare il tutto con la versione Beta1 e, si spera, successive di Symfony2.

Ho voluto provare ad implementare il caricamento di fixtures in FooAppsHelloBundle 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.

Per chi, come ho fatto io per questi primi esperimenti, utilizza Symfony Standard Edition 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

git clone git://github.com/doctrine/data-fixtures.git vendor/doctrine-data-fixtures

Chi non ha git (ma converrebbe iniziare ad imparare ad usarlo), può ottenere il pacchetto da GitHub.

Per le ragioni viste nell'articolo precedente bisogna anche registrare il namespace

// app/autoload.php

/* ... */
$loader->registerNamespaces(array(
    'Symfony'            => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
    'Sensio'             => __DIR__.'/../vendor/bundles',
    'JMS'                => __DIR__.'/../vendor/bundles',
    /* Aggiungere la riga seguente */
    'Doctrine\\Common\\DataFixtures'   => __DIR__.'/../vendor/doctrine-data-fixtures/lib',
    /* ... */
));

A partire dalla versione Beta1 di Symfony2, il comando per il caricamento di fixtures si trova in un bundle separato DoctrineFixturesBundle che bisogna installare perché non incluso nella versione Standard.

git clone git://github.com/symfony/DoctrineFixturesBundle.git vendor/bundles/Symfony/Bundle/DoctrineFixturesBundle

Il bundle va poi registrato

// app/AppKernel.php

    public function registerBundles()
    {
        return array(
            /* ... */
            new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),
        );
    }

Si può a questo punto creare la nostra classe per il caricamento delle fixtures.

// 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->setName('Fabien');
        $manager->persist($friend);

        /* Creare altre istanze di Friend qui se necessario */

        $manager->flush();
    }
}

Cosa succede nel metodo load() dovrebbe essere abbastanza chiaro: si creano le istanze dell'entità Friend, se ne impostano le proprietà, se ne chiede la persistenza sul database all'entity manager, infine flush() (notare che viene invocato una sola volta alla fine) salva fisicamente i dati sul database.

Fatto tutto questo è possibile usare il seguente comando

php app/console doctrine:fixtures:load --fixtures=src/FooApps/HelloBundle/DataFixtures/ORM

Se si omette l'opzione --fixtures, saranno caricate con un solo comando le fixtures di tutti i bundle del progetto per i quali sia definita una classe loader.

mercoledì 23 marzo 2011

Symfony2, installare un bundle

Penso possa essere utile, anche solo come promemoria, indicare i passi per installare un bundle nella propria applicazione utilizzando come esempio FooAppsHelloBundle 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

git clone git://github.com/mgiagnoni/HelloBundle.git src/FooApps/HelloBundle

Git creerà la cartella src/FooApps/HelloBundle e vi copierà il contenuto del repository.

Se no dalla pagina principale del bundle su GitHub con il pulsante Downloads scarichiamo un archivio compresso del sorgente, creiamo la cartella src/FooApps nel nostro progetto e ci decomprimiamo l'archivio: sarà creata una cartella mgiagnoni-HelloBundle-7cc65167 (la parte finale cambia perché è l'hash dell'ultimo commit) che va rinominata in HelloBundle.

Ci si potrebbe chiedere la ragione di queste due cartelle nidificate (FooApps/HelloBundle). Symfony utilizza un sistema standard per l'autoloading 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 (maggiori dettagli per chi è interessato).

Nome qualificato della classe del nostro bundle: FooApps\HelloBundle\FooAppsHelloBundle Percorso relativo del file: FooApps/HelloBundle/FooAppsHelloBundle.php

Possiamo facilmente verificare che anche il bundle di esempio AcmeDemoBundle installato con Symfony Standard Edition è organizzato nello stesso modo.

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, FooApps nel nostro esempio). Questo si fa registrando il namespace nel file app/autoload.php

// app/autoload.php

$loader->registerNamespaces(array(
    /* ... */
    'FooApps' => __DIR__.'/../src',
);

L'operazione è necessaria ogni volta che si crea un nuovo vendor. Se avessi creato un AcmeHelloBundle 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 FooApps.

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.

Va poi registrato il bundle

// app/AppKernel.php

public function registerBundles()
{
    return array(
        /* ... */
        new FooApps\HelloBundle\FooAppsHelloBundle(),
    );
}

Il bundle definisce delle rotte che vanno importate nella configurazione del progetto.

# app/config/routing.yml

fooapps_hello:
    resource: "@FooAppsHelloBundle/Resources/config/routing.yml"
    prefix: /hello_friend

Il bundle utilizza Doctrine per cui deve essere inserito nel parametro mappings della configurazione dell'orm (rimando maggiori dettagli su questo ad un'altra volta).

# app/config/config.yml

# ...
doctrine:
        # ...
    orm:
        # ...
        default_entity_manager: default
        entity_managers:
            default:
                mappings:
                    # ...
                    FooAppsHelloBundle: ~

Infine bisogna creare database e tabella dove saranno salvati gli oggetti Friend. Dalla cartella del progetto si esegue

$ php app/console doctrine:database:create
$ php app/console doctrine:schema:create

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 app/config/parameters.ini).

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.

lunedì 21 marzo 2011

Symfony2, struttura di un bundle

Come ho accennato nell'articolo precedente, in Symfony2 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.

Chi proviene dalla versione (verrebbe da dire precedente, ma è ancora l'attuale) di Symfony avrà quasi sicuramente letto il tutorial Jobeet. Se ricordate, in tutta la prima parte il codice è organizzato in applicazioni (frontend, backend) e moduli. Poi il giorno 20, 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 sfJobeetPlugin: 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.

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.

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.

La struttura elementare di un bundle si capisce anche solo guardando l'organizzazione dei file nel repository di FooAppsHelloBundle anche se vedremo che le cose possono diventare molto più complesse di così.

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)

(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):

Nome Vendor (FooApps) + nome bundle (Hello) + suffisso Bundle

Se si apre FooAppsHelloBundle.php 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 namespace (FooApps\HelloBundle) anch'esso costruito seguendo una convenzione precisa:

Nome Vendor \ nome bundle + suffisso Bundle

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!'.

Accenno brevemente agli altri file perché quasi tutti meritano una trattazione approfondita che sarà fatta nelle prossime puntate.

(2) Controller, il nostro bundle ne usa due. Ogni classe controller ha dei metodi pubblici detti azioni.

(3) Entità, a noi serve solo una entità Friend. 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à.

(4) Classe del form per la creazione e modifica di un oggetto Friend.

(6) Definizioni delle rotte.

(7) Regole di validazione dell'entità Friend.

(8) Documentazione.

(9) Template. L'esempio utilizza il linguaggio Twig per i template.

Non poco per un "Hello world!" anche se più evoluto rispetto alla versione base. Vedremo tutto un po' per volta.

sabato 19 marzo 2011

Un semplice bundle per iniziare con Symfony2

L'avvicinarsi della versione stabile di Symfony2 (ma c'è ancora un po' da aspettare, le ultime notizie danno il rilascio di una Release Candidate ad inizio aprile), mi ha fatto decidere a scrivere un semplice bundle sperimentale.

Per chi non lo sapesse ancora, in Symfony2 non esistono più applicazioni, moduli e plugin, ma solo bundle.

In particolare ho realizzato una evoluzione :) del bundle 'Hello world!' che si trova in Symfony2 Standard Edition (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.

Ho pure avuto il coraggio di metterlo su GitHub, FooAppsHelloBundle, con le istruzioni per installarlo se qualcuno lo volesse provare.

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.