[Parte 4] - Il modello Commenti: Aggiunta di commenti, repository Doctrine e migrazioni ¶
Panoramica ¶
Questo capitolo si baserà sul modello di blog che abbiamo definito nel capitolo precedente. Creeremo il modello di commento, che si occuperà dei commenti dei post , sarà introdotto per creare le relazioni tra i modelli, siccome un post del blog può contenere molti commenti. Useremo la Doctrine 2 QueryBuilder e Doctrine 2 classi Repository per recuperare entità dal database.e Il concetto di Doctrine 2 Migrazioni che fornisce un modo programmatico per distribuire le modifiche al database. Alla fine di questo capitolo avrete creato il modello commento legato insieme con il modello blog. Avremo anche creato la home page, e fornito la possibilità per gli utenti di inviare commenti per un post sul blog.
La Homepage¶
Inizieremo questo capitolo con la costruzione della homepage. Come in vero fashion blogger verranno visualizzati i frammenti di ogni post sul blog, ordinati dal più recente al più vecchio. Il post completo sarà disponibile tramite link alla pagina show blog. Che abbiamo già costruito .
Retrieving the blogs: Querying the model¶
Per visualizzare i blog, abbiamo bisogno di recuperarli dal database. Doctrine 2 fornisce la Dottrina Query Language (DQL) e un QueryBuilder per raggiungere questo obiettivo (È anche possibile eseguire SQL crudo attraverso Doctrine 2, ma questo metodo è sconsigliato in quanto toglie le astrazioni di Dcttrine 2 dal database . Useremo il QueryBuilder in quanto fornisce un bel modo orientato agli oggetti per i generare DQL, che possiamo usare per interrogare il database. Andiamo ad aggiornare l' index action della pagina controller che si trova in src / Blogger / BlogBundle / Controller / PageController.php per prelevare il blog dal database.
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getEntityManager();
$blogs = $em->createQueryBuilder()
->select('b')
->from('BloggerBlogBundle:Blog', 'b')
->addOrderBy('b.created', 'DESC')
->getQuery()
->getResult();
return $this->render('BloggerBlogBundle:Page:index.html.twig', array(
'blogs' => $blogs
));
}
// ..
}
Cominciamo ad ottenere un'istanza del QueryBuilder dal EntityManager . Questo ci permette di iniziare a costruire la query utilizzando i molti metodi che QueryBuilder fornisce. L'elenco completo dei metodi disponibili sono visibili tramite la QueryBuilder documentazione. Un buon punto di partenza è helper methods . Questi sono i metodi che usiamo, come select(), from() and addOrderBy().Come nel caso di interazioni precedenti con Doctrine 2, possiamo usare la notazione breve per fare riferimento al Blog entità via BloggerBlogBundle: Blog (ricordate questo è lo stesso di Blogger \ BlogBundle \ Entity Blog \ ). Quando abbiamo finito specificando i criteri per la query, che chiamiamo GetQuery () che restituisce una DQL esempio. Non siamo in grado di ottenere risultati dal QueryBuilder oggetto, dobbiamo sempre convertirlo ad un DQL prima istanza. Il DQL fornisce ilmetodo getResult () che restituisce un insieme di entità blog. Vedremo in seguito che il DQL istanza ha un certo numero di metodi per risultati che ritornano tra getSingleResult () e getArrayResult () .
The View¶
Ora abbiamo una collezione di soggetti blog che abbiamo bisogno di visualizzarli. Sostituire il contenuto del modello homepage in src / Blogger / BlogBundle / Resources / views / Page / index.html.twig con il seguente.
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block body %}
{% for blog in blogs %}
<article class="blog">
<div class="date"><time datetime="{{ blog.created|date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div>
<header>
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2>
</header>
<img src="{{ asset(['images/', blog.image]|join) }}" />
<div class="snippet">
<p>{{ blog.blog(500) }}</p>
<p class="continue"><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">Continue reading...</a></p>
</div>
<footer class="meta">
<p>Comments: -</p>
<p>Posted by <span class="highlight">{{blog.author}}</span> at {{ blog.created|date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
</article>
{% else %}
<p>There are no blog entries for symblog</p>
{% endfor %}
{% endblock %}
Qui abbiamo una delle strutture di controllo di twig for..else..endfor . Se non avete utilizzato un motore di template prima probabilmente avrete familiarità con il seguente frammento di codice PHP.
<?php if (count($blogs)): ?>
<?php foreach ($blogs as $blog): ?>
<h1><?php echo $blog->getTitle() ?><?h1>
<!-- rest of content -->
<?php endforeach ?>
<?php else: ?>
<p>There are no blog entries</p>
<?php endif ?>
La struttura di controllo for..else..endfor è un modo molto più pulito di realizzare questo compito. La maggior parte del codice all'interno del template homepage si occupa di emettere le informazioni blog in HTML. Tuttavia, ci sono alcune cose che dobbiamo ricordare. In primo luogo, ci avvaliamo della funzione Twig path per generare la rotta per la pagina show blog. Vediamo anche la richiesta di un ID che deve essere presente nell' URL, dobbiamo passare questo come argomento nella funzione path . Come nell'esempio qui sotto.
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2>
In secondo luogo abbiamo in uscita il contenuto del blog utilizzando <p> {{ blog.blog (500) }} </ p> . I' argomento 500 che passiamo, è la lunghezza max del post che vogliamo ricevere indietro dalla funzione. Per far funzionare tutto questo abbiamo bisogno di aggiornare,il metodogetBlog che Doctrine 2 genera per noi in precedenza. Aggiornare il metodo getBlog nel Blog entità in src / Blogger / BlogBundle / Entità / Blog.php .
// src/Blogger/BlogBundle/Entity/Blog.php
public function getBlog($length = null)
{
if (false === is_null($length) && $length > 0)
return substr($this->blog, 0, $length);
else
return $this->blog;
}
Siccome il comportamento usuale del metodo getBlog dovrebbe essere quello di restituire l'intero post del blog, abbiamo impostato il parametro $ length a un valore predefinito null. Se viene passato null viene ritornato l'intero blog.
Ora, se si punta il browser a http://symblog.dev/app_dev.php/ si dovrebbe vedere la home page che mostra le ultime voci di post del blog. Si dovrebbe anche essere in grado di passare al post completo per ogni voce facendo clic sul titolo del blog o del link 'continuare a leggere ...'

Mentre possiamo fare le interrogazioni per le entità nel controllore,ma non è il posto migliore L'interrogazione sarebbe meglio collocarla all'esterno del controllore per diversi motivi:
- Saremmo in grado di riutilizzare la query in altre parti dell' applicazione , senza duplicare il codice QueryBuilder.
- Possiamo modificare il codice QueryBuilder ,separatamente se dobbiamo apportare modifiche in futuro.
- Separare la query e il controllore ci permetterebbe di testare la query indipendentemente dal controller.
Doctrine 2 fornisce le classi Repository per facilitare questo.
Doctrine 2 Repositories¶
Abbiamo già introdotto le basi per le classi di Doctrine 2 repository nel capitolo precedente, quando abbiamo creato la pagina di blog show. Abbiamo usato l'implementazione predefinita della classe Doctrine\ORM\EntityRepository per recuperare un entità blog dal database tramite il metodo find ().Siccome si desidera creare una query personalizzata, abbiamo bisogno di creare un repository personalizzato. Dctrine 2 è in grado di aiutare in questo compito. Aggiornare i metadati dell'entità Blog nel file src / Blogger / BlogBundle / Entità / Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
}
Si può vedere che abbiamo specificato il percorso namespace per la classe BlogRepository alla quale questa entità è associata. Avendo aggiornato i metadati , abbiamo bisogno di eseguire nuovamente il task doctrine:generate:entities
$ php app/console doctrine:generate:entities Blogger
Dottrina 2 avrà creato la classe per la shell BlogRepository in src / Blogger / BlogBundle / Repository / BlogRepository.php ..
<?php
// src/Blogger/BlogBundle/Repository/BlogRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends EntityRepository
{
}
La classe BlogRepository estende la classe EntityRepository che fornisce il metodo find () che abbiamo usato in precedenza.i Aggiornare la
classe BlogRepository , spostando il codice QueryBuilder dalla pagina di controllo in esso.<?php
// src/Blogger/BlogBundle/Repository/BlogRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends EntityRepository
{
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}
}
Abbiamo creato il metodo getLatestBlogs che estrarra le ultime entrate del blog, nello stesso modo del codice del controller QueryBuilder c. Nella classe repository abbiamo accesso diretto alla QueryBuilder attraverso il metodo createQueryBuilder (). Abbiamo anche aggiunto un parametro di default $ limit in modo da poter limitare il numero di risultati da restituire. Il risultato della ricerca è molto simile a come è stato nel controllore. Avrete notato che non abbiamo bisogno di specificare l'entità da utilizzare tramite il metodo from (). Questo perché siamo all'interno della BlogRepository che è associata già con il Blog entità. Se guardiamo l'attuazione del metodo createQueryBuilder nella classe EntityRepository possiamo vedere il metodo from () che viene chiamato per noi.
// Doctrine\ORM\EntityRepository
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}
Infine, aggiorniamo la pagina del controller index azione per utilizzare la BlogRepository
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getEntityManager();
$blogs = $em->getRepository('BloggerBlogBundle:Blog')
->getLatestBlogs();
return $this->render('BloggerBlogBundle:Page:index.html.twig', array(
'blogs' => $blogs
));
}
// ..
}
Ora, quando si aggiorna la homepage dovrebbe mostrare esattamente lo stesso di prima. Tutto quello che abbiamo fatto è refactoring il nostro codice in modo che le classi sono corrette .
Maggiori informazioni sul modello: Creazione del Entity commento ¶
I blog sono solo metà della storia quando si tratta di blogging. Abbiamo anche bisogno di permettere ai lettori la possibilità di commentare i post del blog. Tali osservazioni devono anche essere mantenute, e collegato al Blog entità siccome un blog può contenere molti commenti.
Inizieremo definendo le basi della classe commento entità. Creare un nuovo file in src / Blogger / BlogBundle / Entità / Comment.php e incollare il seguente.
<?php
// src/Blogger/BlogBundle/Entity/Comment.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\CommentRepository")
* @ORM\Table(name="comment")
* @ORM\HasLifecycleCallbacks()
*/
class Comment
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $user;
/**
* @ORM\Column(type="text")
*/
protected $comment;
/**
* @ORM\Column(type="boolean")
*/
protected $approved;
/**
* @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments")
* @ORM\JoinColumn(name="blog_id", referencedColumnName="id")
*/
protected $blog;
/**
* @ORM\Column(type="datetime")
*/
protected $created;
/**
* @ORM\Column(type="datetime")
*/
protected $updated;
public function __construct()
{
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
$this->setApproved(true);
}
/**
* @ORM\preUpdate
*/
public function setUpdatedValue()
{
$this->setUpdated(new \DateTime());
}
}
La maggior parte di ciò che si vede qui,lo abbiamo già trattato nel capitolo precedente, tuttavia abbiamo usato i metadati per creare un link al Blog entità. Sicome il commento è per un blog, abbiamo impostato un collegamento nell'entità commento al Blog soggetto a cui appartiene. Facciamo questo specificando un collegamento ManyToOne inteso a promuovere le entità del Blog. Si precisa inoltre che l'inverso di questo collegamento sarà disponibile tramite i commenti . Per creare questa inversione , abbiamo bisogno di aggiornare le entità del Blog in modo che Doctrine 2 sa che un blog può contenere molti commenti. Aggiornare il Blog entità in src / Blogger / BlogBundle / Entità / Blog.php per aggiungere questa mappatura.
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
/**
* @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;
// ..
public function __construct()
{
$this->comments = new ArrayCollection();
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
}
// ..
}
Ci sono alcuni cambiamenti da evidenziare qui. Per prima cosa sono stati aggiunti i metadati a $ comments. Ricordate nel capitolo precedente non abbiamo aggiunto gli eventuali metadati per questo membro perchè non volevamo che Doctrine 2 li persistesse. Questo è ancora vero, però, ora vogliamo che Doctrine 2 sia in grado di popolare il membro con le pertinenti entità Commento. Questo è ciò che i metadati realizzano. In secondo luogo,si richiede che Doctrine 2 imposti il membro $ comments a un oggetto ArrayCollection. Facciamo questo nel costruttore . Si noti inoltre l' use per importare la dichiarazione ArrayCollection classe.
Sicome abbiamo creato l'entitàl commento , e aggiornato il Blog entità, questo ci consente di generare le funzioni di accesso con Doctrine 2.. Eseguire il seguente task Doctrine 2 per raggiungere questo obiettivo.
$ php app/console doctrine:generate:entities Blogger
Entrambe le entità dovrebbe essere up-to-date con i metodi di accesso corretti. Noterete anche la classe CommentRepository che è stata creata in src / Blogger / BlogBundle / Repository / CommentRepository.php come abbiamo specificato questa nei metadati.
Infine abbiamo bisogno di aggiornare il database in modo da riflettere le modifiche apportate alle nostre entità. Potremmo usare il task
doctrine:schema:update come segue per fare questo, ma invece introdurremo Dcttrine 2 Migrazioni.
$ php app/console doctrine:schema:update --force
Doctrine 2 Migrations¶
The Doctrine 2 Migrations extension è bundle che non viene con la Symfony2 Standard Distribution,dobbiamo installarli come abbiamo fatto con Data Fixtures extension . Apriamo il file deps e aggiungiamo il bundle Doctrine 2 Migrations extension nel seguente modo..
[doctrine-migrations]
git=http://github.com/doctrine/migrations.git
[DoctrineMigrationsBundle]
git=http://github.com/symfony/DoctrineMigrationsBundle.git
target=/bundles/Symfony/Bundle/DoctrineMigrationsBundle
Facciamo l'aggiornamento dei vendor per aggiungere questi cambiamenti.
$ php bin/vendors install
Nota
Se tu non hai git installato nella tua macchina hai bisogno di scaricarli manualmente.
doctrine-migrations extension: Download la corrente versione da GitHub ed estrarli nel seguente percorso vendor/doctrine-migrations.
DoctrineMigrationsBundle: Download la corrente versione da GitHub ed estrarli nel seguente percorso vendor/bundles/Symfony/Bundle/DoctrineMigrationsBundle.
Prossimo aggiornamento è nel file app/autoload.php per registrare il nuovo namespace. Siccome Doctrine 2 Migrations sono anche in Doctrine\DBAL namespace devono essere messi in Doctrine\DBAL impostando un nuovo percorso.I Namespaces sono controllati dall'alto al basso cosi dobbiamo impostare i namespace che devono essere controllati prima di altri.
// app/autoload.php
// ...
$loader->registerNamespaces(array(
// ...
'Doctrine\\DBAL\\Migrations' => __DIR__.'/../vendor/doctrine-migrations/lib',
'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib',
// ...
));
Ora andiamo a registrare il bundle nel kernel in app/AppKernel.php.// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(),
// ...
);
// ...
}
Warning
The Doctrine 2 Migrations library è ancora in alpha state cosi l'uso su un server di produzione è sconsigliato per ora.
Siamo ora pronti per aggiornare il database in base alle modifiche dell'i entità. Questo è un processo in 2 fasi. In primo luogo abbiamo bisogno di ottenere con Doctrine 2 Migrazioni le differenze tra le entità e lo schema di database corrente. Questo viene fatto con ladoctrine:migrations:diff . In secondo luogo abbiamo bisogno di eseguire realmente la migrazione basata sulla differenza ottenuta in precedenza. Questo viene fatto con doctrine:migrations:migrate.
Eseguiamo i due task e aggiorniamo lo schema del database..
$ php app/console doctrine:migrations:diff
$ php app/console doctrine:migrations:migrate
Il database ora riflettere le ultime modifiche di entità e contiene la nuova tabella commenti.
Nota
Noterete anche una nuova tabella nel database chiamata migration_versions . Questa memorizza i numeri di versione di migrazione in modo che l' attività di migrazione è in grado di vedere qual'è la versione corrente del database .
Nota
Doctrine 2 migrazioni è un ottimo modo per aggiornare il database di produzione come le modifiche possono essere fatte programatically. Questo significa che possiamo integrare questa attività in uno script di distribuzione in modo che il database viene aggiornato automaticamente quando si distribuisce una nuova versione dell'applicazione. Doctrine 2 Migrazioni ci permettono inoltre di ripristinare le modifiche che ogni migrazione ha creato con i metodi up e down .Per ripristinare una versione precedente è necessario specificare il numero di versione che si desidera ripristinare utilizzando il task seguente seguente.
$ php app/console doctrine:migrations:migrate 20110806183439
Data Fixtures: Revisited¶
Ora abbiamo l'entità commento questo ci permette di aggiungere alcune fixture per esso. E 'sempre una buona idea aggiungere alcune fixture ogni volta che si crea un'entità. Sappiamo che un commento deve avere un relativo Blog di entità data la sua configurazione in questo modo nei metadati, Perciò durante la creazione di fixture per l'entità Comment sarà necessario specificare l'entità l Blog. Abbiamo già creato le fixture per l'entità Blog possiamo semplicemente aggiornare il file per aggiungere le entità Comment. Questo è forse gestibile per ora, ma cosa succede quando in seguito aggiungerete gli utenti, le categorie del blog, e un intero carico di altre entita al nostro bundle. Un modo migliore sarebbe quello di creare un nuovo file per le fixture dell'entitài Comment Il problema di questo approccio è come possiamo accedere alle entità blog da le blog fixture..
Fortunatamente questo può essere ottenuto facilmente impostando i riferimenti agli oggetti in un unico file di fixture cosi che altri dispositivi possono accedere. Aggiornare le entità blog di DataFixtures situati in src / Blogger / BlogBundle / DataFixtures / ORM / BlogFixtures.php con il seguente. I cambiamenti da notare qui sono l'estensione della classe AbstractFixture e gli strumenti del OrderedFixtureInterface . Si noti inoltre le 2 nuove dichiarazioni use per importare queste classi.
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Blogger\BlogBundle\Entity\Blog;
class BlogFixtures extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
// ..
$manager->flush();
$this->addReference('blog-1', $blog1);
$this->addReference('blog-2', $blog2);
$this->addReference('blog-3', $blog3);
$this->addReference('blog-4', $blog4);
$this->addReference('blog-5', $blog5);
}
public function getOrder()
{
return 1;
}
}
Aggiungiamo i riferimenti alle entità blog utilizzando il metodo AddReference (). Il primo parametro è un identificatore di riferimento si può utilizzare per recuperare l'oggetto più tardi. Infine, dobbiamo attuare il metodo GetOrder () per specificare l'ordine di caricamento delle fixture . Blog deve essere caricato prima dei commenti in modo da restituire 1.
Comment Fixtures¶
Siamo ora pronti per definire alcune fixture per nostre entità commentoCreare un file di fixture in src / Blogger / BlogBundle / DataFixtures / ORM / CommentFixtures.php e aggiungere il seguente contenuto:
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Blogger\BlogBundle\Entity\Comment;
use Blogger\BlogBundle\Entity\Blog;
class CommentFixtures extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
$comment = new Comment();
$comment->setUser('symfony');
$comment->setComment('To make a long story short. You can\'t go wrong by choosing Symfony! And no one has ever been fired for using Symfony.');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('David');
$comment->setComment('To make a long story short. Choosing a framework must not be taken lightly; it is a long-term commitment. Make sure that you make the right selection!');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Anything else, mom? You want me to mow the lawn? Oops! I forgot, New York, No grass.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Are you challenging me? ');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:15:20"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Name your stakes.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:18:35"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('If I win, you become my slave.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:22:53"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Your SLAVE?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:25:15"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('You wish! You\'ll do shitwork, scan, crack copyrights...');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:46:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('And if I win?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 10:22:46"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Make it my first-born!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 11:08:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Make it our first-date!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-24 18:56:01"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('I don\'t DO dates. But I don\'t lose either, so you\'re on!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-25 22:28:42"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Stanley');
$comment->setComment('It\'s not gonna end like this.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gabriel');
$comment->setComment('Oh, come on, Stan. Not everything ends the way you think it should. Besides, audiences love happy endings.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Mile');
$comment->setComment('Doesn\'t Bill Gates have something like that?');
$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gary');
$comment->setComment('Bill Who?');
$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$manager->flush();
}
public function getOrder()
{
return 2;
}
}
Con le modifiche che abbiamo fatto la classe BlogFixtures che estende la classe CommentFixtures che estende anche AbstractFixture e implementa OrderedFixtureInterface dobbiamo anche implementare il metodo GetOrder (). Questa volta abbiamo impostato il valore di ritorno a 2, assicurando che queste fixture verranno caricate dopo le fixture blog.
Possiamo anche vedere come i riferimenti aile entità blog che abbiamo creato in precedenza vengono utilizzati.
$comment->setBlog($manager->merge($this->getReference('blog-2')));
Siamo ora pronti per caricare le fixture nel database.
$ php app/console doctrine:fixtures:load
Visualizzazione dei Commenti ¶
Ora possiamo visualizzare i commenti relativi ad ogni post. Cominciamo con l'aggiornamento del metodo CommentRepository per recuperare gli ultimi commenti approvati per un post sul blog.
Commento Repository ¶
Aprire la classe CommentRepository in src / Blogger / BlogBundle / Repository / CommentRepository.php e sostituire il contenuto con quanto segue.
<?php
// src/Blogger/BlogBundle/Repository/CommentRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* CommentRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CommentRepository extends EntityRepository
{
public function getCommentsForBlog($blogId, $approved = true)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where('c.blog = :blog_id')
->addOrderBy('c.created')
->setParameter('blog_id', $blogId);
if (false === is_null($approved))
$qb->andWhere('c.approved = :approved')
->setParameter('approved', $approved);
return $qb->getQuery()
->getResult();
}
}
Il metodo che abbiamo creato sarà per recuperare i commenti per un post sul blog. Per fare questo abbiamo bisogno di aggiungere una clausola where per la nostra query. La clausola utilizza un parametro denominato che viene impostato utilizzando il metodo setParameter (). Si dovrebbe sempre utilizzare i parametri invece di impostare i valori direttamente nella query in questo modo
->where('c.blog = ' . blogId)
In questo esempio il valore di $ blogId non sarà sanizzato e potrebbe lasciare la query aperta ad un attacco di tipo SQL injection.
Blog Controller¶
Poi abbiamo bisogno di aggiornare la show action nel Blog controller per recuperare i commenti per il blog. Aggiornare il Blog controller che si trova in src / Blogger / BlogBundle / Controller / BlogController.php
// src/Blogger/BlogBundle/Controller/BlogController.php
public function showAction($id)
{
// ..
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog post.');
}
$comments = $em->getRepository('BloggerBlogBundle:Comment')
->getCommentsForBlog($blog->getId());
return $this->render('BloggerBlogBundle:Blog:show.html.twig', array(
'blog' => $blog,
'comments' => $comments
));
}
Usiamo il nuovo metodo su CommentRepository per recuperare i commenti approvati per il blog. Ia collezione $ comments viene anche inviata al template.
Blog show template¶
Ora abbiamo un elenco di commenti per il blog si può aggiornare il template show blog per visualizzare i commenti. Potremmo semplicemente posizionare il rendering dei commenti direttamente nel template show blog, ma i commenti sono diversi dalle loro entità, meglio separare il rendering in un altro template, e includere poi tale template. Questo ci permettera di riutilizzare il template di rendering commento altrove nell'applicazione. Aggiornare il template di show blog in src / Blogger / BlogBundle / Resources / views / Blog / show.html.twig
{# src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
<section class="previous-comments">
<h3>Comments</h3>
{% include 'BloggerBlogBundle:Comment:index.html.twig' with { 'comments': comments } %}
</section>
</section>
{% endblock %}
Si può vedere l'utilizzo di un nuovo tag Twig include . Ciò include il contenuto del template specificato per BloggerBlogBundle:Comment:index.html.twig. Possiamo anche passare altri argomenti al modello. In questo caso, abbiamo bisogno di passare un insieme di entità commento per il rendering.
Comment show template¶
Il BloggerBlogBundle:Comment:index.html.twig che stiamo includendo non esiste ancora quindi abbiamo bisogno di crearlo . Dato che questo è solo un template, non abbiamo bisogno di creare una rotta o un controller per questo, abbiamo solo bisogno di creare il file di template. in src / Blogger / BlogBundle / Resources / views / comment / index.html.twig e incollare il seguente codice.
{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #}
{% for comment in comments %}
<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}" id="comment-{{ comment.id }}">
<header>
<p><span class="highlight">{{ comment.user }}</span> commented <time datetime="{{ comment.created|date('c') }}">{{ comment.created|date('l, F j, Y') }}</time></p>
</header>
<p>{{ comment.comment }}</p>
</article>
{% else %}
<p>There are no comments for this post. Be the first to comment...</p>
{% endfor %}
Come potete vedere abbiamo inserito una collezione di entità commento e visualizzato i commenti. Introduciamo anche una delle altre belle funzioni di Twig, il cycle function. Questa funzione permette di scorrere i valori del array che vengono passati progressivamente . L'attuale valore di iterazione del ciclo si ottiene tramite la speciale variabile loop.index0. Questa mantiene un conteggio delle iterazioni del ciclo, a partire da 0. Ci sono una serie di altre variabili speciali disponibili quando siamo all'interno di un blocco di codice loop. Si può inoltre notare l'impostazione di un ID HTML all'elemento articolo. Questo ci permetterà di creare in seguito dei permalink ai commenti creati.
Comment show CSS¶
Infine, andiamo adi aggiungere un po di 'CSS per mantenere i commenti in un certo stile. Aggiornare il foglio di stile in src / Blogger / BlogBundle / Resorces / public / css / blog.css
/** src/Blogger/BlogBundle/Resorces/public/css/blog.css **/
.comments { clear: both; }
.comments .odd { background: #eee; }
.comments .comment { padding: 20px; }
.comments .comment p { margin-bottom: 0; }
.comments h3 { background: #eee; padding: 10px; font-size: 20px; margin-bottom: 20px; clear: both; }
.comments .previous-comments { margin-bottom: 20px; }
Nota
Se non si utilizza il metodo di collegamento simbolico per fare riferimento al bundle nella cartella web è necessario eseguire nuovamente il task asset per i copiare le modifiche apportate al CSS.
$ php app/console assets:install web
Se ora diamo uno sguardo ad una delle pagine che mostrano i blog, ad esempio http://symblog.dev/app_dev.php/2 si dovrebbe vedere l'output blog commenti.

Aggiunta di commenti ¶
L'ultima parte del capitolo inserirà la funzionalità per gli utenti per aggiungere commenti al post del blog . Ciò sarà possibile attraverso un form su la pagina show blog. Siamo già stati introdotti alla creazione di form in Symfony2 quando abbiamo creato il form di contatto. Piuttosto che creare manualmente il form dei commenti, possiamo usare Symfony2 per fare questo per noi. Eseguire l'operazione seguente per generare la classe CommentType per l'entità commento.
$ php app/console generate:doctrine:form BloggerBlogBundle:Comment
Noterete anche qui, l'uso della versione corta per specificare le entità commento.
Nota
Tu avrai notato anche il task doctrine:generate:form . Questo genera un differente namespace.
il task generate form ha creato la classe CommentType in src/Blogger/BlogBundle/Form/CommentType.php.
<?php
// src/Blogger/BlogBundle/Form/CommentType.php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class CommentType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('user')
->add('comment')
->add('approved')
->add('created')
->add('updated')
->add('blog')
;
}
public function getName()
{
return 'blogger_blogbundle_commenttype';
}
}
Abbiamo già esplorato ciò che sta accadendo qui nella precedente classe EnquiryType. Potremmo iniziare personalizzando questa classe adesso, ma andiamo prima a visualizzare il form .
Visualizzazione del form dei commenti ¶
Sicome si desidera che l'utente aggiungere commenti dalla pagina di blog show, potremmo creare il form nella show action del Blog controller e il rendering del template direttamente nello show template. Tuttavia, sarebbe meglio separare il codice come abbiamo fatto con la visualizzazione dei commenti. La differenza tra mostrare i commenti e il form dei commenti è che il form deve essere processato , quindi questa volta è necessario un controller. Questo introduce un metodo leggermente diverso da quanto sopra, dove abbiamo appena incluso un modello
Routing¶
Abbiamo bisogno di creare una nuova rotta per gestire il processo di invio del form. Aggiungere un nuovo percorso al file di routing in src / Blogger / BlogBundle / Resources / config / routing.yml .
BloggerBlogBundle_comment_create:
pattern: /comment/{blog_id}
defaults: { _controller: BloggerBlogBundle:Comment:create }
requirements:
_method: POST
blog_id: \d+
The controller¶
Quindi, abbiamo bisogno di creare il nuovo controller di Commento che abbiamo indicato in precedenza. Creare un file situato in src / Blogger / BlogBundle / Controller / CommentController.php e incollare il seguente codice.
<?php
// src/Blogger/BlogBundle/Controller/CommentController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Blogger\BlogBundle\Entity\Comment;
use Blogger\BlogBundle\Form\CommentType;
/**
* Comment controller.
*/
class CommentController extends Controller
{
public function newAction($blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$form = $this->createForm(new CommentType(), $comment);
return $this->render('BloggerBlogBundle:Comment:form.html.twig', array(
'comment' => $comment,
'form' => $form->createView()
));
}
public function createAction($blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$request = $this->getRequest();
$form = $this->createForm(new CommentType(), $comment);
$form->bindRequest($request);
if ($form->isValid()) {
// TODO: Persist the comment entity
return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
return $this->render('BloggerBlogBundle:Comment:create.html.twig', array(
'comment' => $comment,
'form' => $form->createView()
));
}
protected function getBlog($blog_id)
{
$em = $this->getDoctrine()
->getEntityManager();
$blog = $em->getRepository('BloggerBlogBundle:Blog')->find($blog_id);
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog post.');
}
return $blog;
}
}
Creiamo 2 azioni nel controller commento , una per new e una per create . La new azione si occupa di visualizzare il modulo dei commenti, il create elabora l'oggetto dell' invio del form dei commenti. Mentre questo può sembrare che occorra una grande quantità di codice, non c'è nulla di nuovo qui, tutto è come nel capitolo 2, quando abbiamo creato il modulo di contatto. Tuttavia, prima di procedere assicuratevi di comprendere appieno ciò che sta accadendo nel controller commento.
La validazione dei form ¶
Non vogliamo che gli utenti siano in grado di presentare osservazioni al blog senza i valori user o commento. Per raggiungere questo torniamo ad osservare i validatori sono stati introdotti nel capitolo 2 durante la creazione del modulo di richiesta. Aggiornare le entità commento in src / Blogger / BlogBundle / Entità / Comment.php con il seguente.
<?php
// src/Blogger/BlogBundle/Entity/Comment.php
// ..
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
// ..
class Comment
{
// ..
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('user', new NotBlank(array(
'message' => 'You must enter your name'
)));
$metadata->addPropertyConstraint('comment', new NotBlank(array(
'message' => 'You must enter a comment'
)));
}
// ..
}
Collochiamo dei vincoli in modo che sia l'utente che i membri del commento non possono essere vuoti. dobbiamo anche impostare il messaggio per cambiare i valori di default. Ricordarsi di aggiungere il namespace per ClassMetadata e NotBlank come mostrato sopra.
The view¶
Poi abbiamo bisogno di creare i 2 template per le azioni del controller new e create. Innanzitutto creare un nuovo file in src / Blogger / BlogBundle / Resources / views / commento / form.html.twig e incollare il seguente codice .
{# src/Blogger/BlogBundle/Resources/views/Comment/form.html.twig #}
<form action="{{ path('BloggerBlogBundle_comment_create', { 'blog_id' : comment.blog.id } ) }}" method="post" {{ form_enctype(form) }} class="blogger">
{{ form_widget(form) }}
<p>
<input type="submit" value="Submit">
</p>
</form>
Lo scopo di questo template è semplice, rendere il form commenti. Noterete anche che l' azione del form è POST per il nuovo percorso che abbiamo creato BloggerBlogBundle_comment_create .
Avanti possiamo i aggiungere il template per la vista create. Creare un nuovo file in src / Blogger / BlogBundle / Resources / views / commento / create.html.twig e incollare il seguente codice.
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}Add Comment{% endblock%}
{% block body %}
<h1>Add comment for blog post "{{ comment.blog.title }}"</h1>
{% include 'BloggerBlogBundle:Comment:form.html.twig' with { 'form': form } %}
{% endblock %}
La create action del controller Comment tratta di processare il form, ed ha anche bisogno di mostrarlo, e non può avere errori . Abbiamo riutilizzato il BloggerBlogBundle: Commento: form.html.twig per rendere il form attuale ed evitare la duplicazione del codice.
Ora andiamo ad aggiornare il template show blog per rendere il form da aggiungere . Aggiornare il template in src / Blogger / BlogBundle / Resources / views / Blog / show.html.twig con il seguente.
{# src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
{# .. #}
<h3>Add Comment</h3>
{% render 'BloggerBlogBundle:Comment:new' with { 'blog_id': blog.id } %}
</section>
{% endblock %}
Usiamo un altro nuovo tag di Twig qui, il render tag. Questo tag rende i contenuti di un controllor nel template. Nel nostro caso abbiamo reso i i contenuti della controller action BloggerBlogBundle:Comment:new
Se ora diamo uno sguardo ad una delle pagineche mostrano blog, come http://symblog.dev/app_dev.php/2 noterete un'eccezione Symfony2 .

Questa eccezione è stata generata dal template BloggerBlogBundle: Blog: show.html.twig. Se guardiamo alla riga 25 BloggerBlogBundle: Blog: show.html.twig possiamo vedere che il problema esiste realmente nel processo di inserimento del
controller BloggerBlogBundle: Commento: creare
{% render 'BloggerBlogBundle:Comment:create' with { 'blog_id': blog.id } %}
Se guardiamo il messaggio di eccezione ci dà qualche informazione in più circa la natura del perché l'eccezione è stata causata.
Entities passed to the choice field must have a “__toString()” method defined
Questo ci sta dicendo che un campo di scelta che stiamo cercando di rendere non hai il metodo __toString () impostato per l'entità associata con il campo di scelta . Un campo di scelta è un elemento del form che offre all'utente una serie di scelte, come un elemento select a (discesa). È forse vi state chiedendo dove stiamo rendendo un campo di scelta nel modulo dei commenti? Se si guarda al form di template commento di nuovo si noterà che il rendering del form utilizza la funzione Twig {{ form_widget (form) }}. Questa funzione emette l'intero form nella sua forma base. Quindi, consente di tornare alla classe viene creato il form da, la classe CommentType. Possiamo vedere che un certo numero di campi vengono aggiunti al modulo attraverso l'oggetto FormBuilder. In particolare si sta aggiungendo un campo blog.
Se ricordate il capitolo 2, abbiamo parlato su come il FormBuilder cercherà di indovinare il tipo di campo di uscita in base ai metadati relativi al campo. Sicome abbiamo impostato un rapporto tra le entità commento e blog , il metodo FormBuilder ha indovinato il commento come una scelta di campo, che consentirebbe all'utente di specificare il post sul blog per attaccare il commento. Questo è il motivo per cui abbiamo una scelta di campo nel form, e perché la Symfony2 eccezione viene generata. Siamo in grado di risolvere questo problema implementando il metodo __toString () nel entità Blog.
// src/Blogger/BlogBundle/Entity/Blog.php
public function __toString()
{
return $this->getTitle();
}
Nota
I messaggi di errore di Symfony2 sono molto istruttivi nella descrizione del problema che si è verificato. Leggere sempre i messaggi di errore in quanto di solito rendono il processo di debug molto più facile. I messaggi di errore forniscono anche una traccia completa dello stack in modo da poter vedere i passi che si stavano facendo per causare l'errore.
Ora, quando si aggiorna la pagina si dovrebbe vedere in uscita il form dei commenti. Noterete anche che sono usciti alcuni campi indesiderati come approved, created, updated e blog. Questo perchè non abbiamo impostato prima la classe CommentType
Nota
Quando viene eseguito il rendering tutti i campi sembrano essere in uscita nel modo corretto . I' user campo è un text campo, il comment è un campo textarea , i due campi DateTime sono un certo numero che ci permette di specificare il tempo, ecc
Questo è dovuto al FormBuilders che è capace di indovinare il tipo di campo del membro che il rendering richiede. E 'in grado di farlo in base ai metadati forniti. Avendo specificato i metadati in modo molto specifico per le entità commento , il FormBuilder è in grado di fare ipotesi precise dei tipi di campo.
Andiamo ora ad aggiornare questa classe in src / Blogger / BlogBundle / Form / CommentType.php per restituire solo i campi di cui abbiamo bisogno.
<?php
// src/Blogger/BlogBundle/Form/CommentType.php
// ..
class CommentType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('user')
->add('comment')
;
}
// ..
}
Ora, quando si aggiorna la pagina solo l'utente e il campo di commento sono in uscita. Se si dovesse inviare il modulo ora, il commento non sarebbe in realtà salvato nel database. Questo perché il modulo controller non fa nulla con le entità commento se il modulo passa la convalida. Quindi, come possiamo mantenere l'entità Commento nel database. Avete già visto come eseguire questa operazione durante la creazione DataFixtures . Aggiornare l'azione create del controller di Commento per mantenere l'entità commento nel l database.
<?php
// src/Blogger/BlogBundle/Controller/CommentController.php
// ..
class CommentController extends Controller
{
public function createAction($blog_id)
{
// ..
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
// ..
}
}
La persistenza dell'entità commento è semplice come una chiamata a persist () e flush () . Ricordate, il form si occupa solo di oggetti PHP, e Doctrine 2 gestisce e persiste questi oggetti. Non vi è alcun collegamento diretto tra l'invio dei dati di un form, ed i dati inviati per persistere sul database.
Si dovrebbe ora essere in grado di aggiungere commenti ai post

Conclusione ¶
Abbiamo fatto buoni progressi in questo capitolo. Il nostro sito blogging sta cominciando a funzionare più come ci si aspetterebbe. Ora abbiamo le basi della homepage e creato l'entità commento. L'utente può ora inviare commenti sui blog e leggere i commenti lasciati dagli altri utenti. Abbiamo visto come creare fixture e utilizzato Doctrine 2 Migrazioni peri mantenere una linea con le modifiche all'entità per gli schemi del database .
Ora vediamo di costruire la barra laterale che comprendere la nuvola di tag e commenti recenti. Si estenderà anche Twig creando i nostri filtri personalizzati. Infine, vedremo come utilizzare bene la libreria Assetic bene per aiutarci a gestire i nostri css,image,js.