📜 ⬆️ ⬇️

Creating a blog on symfony 2.8 lts [Part 4]






Project on Github
You can find out how to install the part of the manual you need in the description of the repository by the link . (For example, if you want to start with this lesson without going through the previous one)

')

Homepage


We will start this part by creating a home page. In a regular blog posts are usually sorted from new to old. The entire recording will be available via the link from the blog page. Since we have already created a route, controller and display for the home page, we can easily update them.

Getting records. Querying the database.


To display blog entries we need to retrieve them from the database. Doctrine 2 provides us with Doctrine Query Language (DQL) and QueryBuilder to achieve this goal (you can also do a normal SQL query through Doctrine 2, but this method is not recommended because it takes us away from the database abstraction that Doctrine 2 provides us. We will use QueryBuilder because it provides a good object-oriented way for us to generate a DQL that we can use for database queries. Let's update the indexAction method in the Page controller src/Blogger/BlogBundle/Controller/PageController.php to get entries from ba s data.
Paste the following code
 // src/Blogger/BlogBundle/Controller/PageController.php class PageController extends Controller { public function indexAction() { $em = $this->getDoctrine() ->getManager(); $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 )); } // .. } 


We started by getting an instance of QueryBuilder from Manager. This will allow you to start creating a query using the many methods QueryBuilder provides us with. A complete list of available methods can be found in the documentation for QueryBuilder. Best to start with helper methods . These include select (), from (), and addOrderBy (). As in the case of previous interactions with Doctrine 2, we can use short annotations to access the Blog entity via the BloggerBlogBundle: Blog ( keep in mind that this is the same as Blogger \ BlogBundle \ Entity \ Blog) . When we have finished specifying the criteria for the query, we call the getQuery () method, which returns an instance of DQL. We cannot get results from the QueryBuilder object, we must always first convert it to a DQL instance. An instance of the DQL object provides us with a getResult () method that returns a collection of Blog entries. Later we will see that an instance of the DQL object has a variety of methods for returning results, including getSingleResult () and getArrayResult ().

Display


Now we have a collection of records and we need to display them. Replace the content of the home page located src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
as follows
 {# 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 %} 



We introduced one of the management structures of Twig, for..else..endfor . If you have not previously used template engines, you will probably be familiar with the following PHP code.
 <?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 ?> 


The Twig for..else..endfor control structure is an easier way to accomplish this. Most of the code in the homepage template concerns the output of blog information in HTML format. However, there are several points that we need to take into account. First, we use the Twig Path function to create routes. Since the blog page requires the ID of the entry passed to the URL, we need to insert it as an argument to the path function. This can be seen in this code snippet:
 <h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id }) }}">{{ blog.title }}</a></h2> 


Secondly, we return content using
 <p>{{blog.blog(500) }}</p> 
The number 500 is the maximum length of the post that we want to get back from the function. To do this, we need to update the getBlog method that Doctrine 2 generated for us earlier. Update the getBlog method in the Blog Entity, located
  src/Blogger/BlogBundle/Entity/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; } 


Since the getBlog method should return the entire blog post, we will set the $ length parameter which will have a default value of null . If null is passed, the entire entry is returned.
Now, if you enter localhost: 8000 into your browser, you should see a home page displaying the latest blog entries. You should also have the opportunity to go to the full blog entry by clicking on the title of the post or on the link " Continue reading ... ".



While we can request entries in the controller, this is not the best place for this. The request will be better placed outside the controller due to a number of reasons:
We will not be able to reuse the query anywhere else in the application, without duplicating the QueryBuilder code.
If we duplicated the QueryBuilder code, we would have to make a few changes in the future if the query would need to be changed.
Separating the request and controller will allow us to test the request independently of the controller.
Doctrine 2 provides repository classes to facilitate this task.

Doctrine 2 Repositories


We already talked a little about the Doctrine 2 Repository classes in the previous chapter when we created the blog page. We used the default implementation of the Doctrine \ ORM \ EntityRepository class to retrieve a blog entry from the database using the find () method. Since we need a user request for the database, we need to create a user repository. Doctrine 2 can help with this task. Update Blog Entity Metadata in src / Blogger / BlogBundle / Entity / Blog.php file
 // src/Blogger/BlogBundle/Entity/Blog.php /** * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\BlogRepository") * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks() */ class Blog { // .. } 


You can see that we have defined the namespace for the BlogRepository class with which this entity is associated. Since we updated Doctrine 2 metadata for the Blog entity, we need to rerun the doctrine: generate: entities command as follows.
 $ php app/console doctrine:generate:entities Blogger\BlogBundle 

Doctrine 2 will create a wrapper around the BlogRepository class located at src/Blogger/BlogBundle/Entity/Repository/BlogRepository.php

 <?php namespace Blogger\BlogBundle\Entity\Repository; /** * BlogRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class BlogRepository extends \Doctrine\ORM\EntityRepository { } 


The BlogRepository class extends the EntityRepository class, which is provided by the find () method that we used earlier. Let's update the BlogRepository class by moving the QueryBuilder code from the Page controller to it.

 <?php namespace Blogger\BlogBundle\Entity\Repository; /** * BlogRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class BlogRepository extends \Doctrine\ORM\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(); } } 


We created a getLatestBlogs method that will return the latest blog entries, in the same way as the QueryBuilder code did in the controller. In the repository class, we have direct access to the QueryBuilder using the createQueryBuilder () method. We also added the default parameter $ limit, so we can limit the number of returned results. The result of the query will be the same as it was with the controller. You may have noticed that we do not need to specify an object using the from () method. This is due to the fact that we are in the BlogRepository, which is associated with the essence of Blog. If we look at the implementation of the createQueryBuilder method in the EntityRepository class, we can see that the from () method was called for us.
 // Doctrine\ORM\EntityRepository public function createQueryBuilder($alias, $indexBy = null) { return $this->_em->createQueryBuilder() ->select($alias) ->from($this->_entityName, $alias, $indexBy); } 

Finally, let's update the indexAction method in the Page controller to use BlogRepository.
 // src/Blogger/BlogBundle/Controller/PageController.php class PageController extends Controller { public function indexAction() { $em = $this->getDoctrine() ->getManager(); $blogs = $em->getRepository('BloggerBlogBundle:Blog') ->getLatestBlogs(); return $this->render('BloggerBlogBundle:Page:index.html.twig', array( 'blogs' => $blogs )); } // .. } 

Now, when we update the homepage, the exact same thing as before will be displayed. All we did was reorganize the code so that the well-formed classes perform the tasks correctly.

Read more about the model: Creating a Comment entity


Entries are only half the story when it comes to blogging. We must also allow readers to comment on blog entries. These comments should also be saved and linked to the essence of the Blog as the entry may contain many comments.
We begin by defining the basis, the Comment entity class. Create a new file located in src/Blogger/BlogBundle/Entity/Comment.php and paste
following
 <?php // src/Blogger/BlogBundle/Entity/Comment.php namespace Blogger\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\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()); } } 


Most of what you see here is covered in the previous section, but we used metadata to create a link to the essence of Blog. Since the comment relates to the post, we set the link in the Comment entity to the Blog entity to which it belongs. We did this by pointing the ManyToOne link to the blog entity. We also indicated that the feedback for this link will be available through comments. To invert, we need to update the Blog Entity so Doctrine 2 will know that the post may contain many comments. Update Blog Entity src/Blogger/BlogBundle/Entity/Blog.php
Insert code
 <?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\Entity\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()); } // .. } 


There are several changes to point out. First, we added metadata to the $ comments object. Remember, in the previous chapter, we didn’t add metadata to this object because we didn’t want Doctrine 2 to save it? This is still the case, however, we want Doctrine 2 to be able to fill this object with the appropriate Comment entries. That is what metadata allows to achieve. Second, Doctrine 2 requires that we set the default value for the $ comments object in the ArrayCollection. We will do this in the constructor. Also note the use statement that imports the ArrayCollection class.

Since we created the Comment entity and updated the Blog entity, let's create access methods. Run the following Doctrine 2 command.
 $ php app/console doctrine:generate:entities Blogger\BlogBundle 


Both entities will be updated with the correct access methods. You will also notice that in
src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php

The CommentRepository class has been created since we specified this in the metadata.

Finally, we need to update the database to reflect the changes in our entities. We could use the doctrine: schema: update command, which is shown below, to do this, but instead we’ll tell you about Doctrine 2 Migrations.
 $ php app/console doctrine:schema:update --force 

Doctrine Migrations 2


The Doctrine 2 Migrations extension and the bundle is not supplied with symfony2, we have to manually install them. Open the composer.json file located in the project root and paste the Doctrine 2 and Bundle Migration dependencies as shown below.
 "require": { // ... "doctrine/doctrine-migrations-bundle": "dev-master", "doctrine/migrations": "dev-master" } 

Next, update the library with the command.
 $ composer update 

This will update all the libraries from Github and install them in the necessary directories.
Now let's register a bundle in a kernel located in app / AppKernel.php
 // app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), // ... ); // ... } 


Now we are ready to update the database to reflect the changes in essence, this process will take place in 2 stages. First, we must instruct Doctrine 2 Migrations to work with the differences between entities and the current database schema. This is done by the doctrine: migrations: diff command. Secondly, we need to perform a migration based on the data created by the first team. This is done by the doctrine: migrations: migrate command.
Run the following 2 commands to update the database schema.
 $ php app/console doctrine:migrations:diff $ php app/console doctrine:migrations:migrate 

The warning shown below is answered with yes .
WARNING! You can’t end up with a schema of changes and data lost. Are you sure you want to continue? (y / n): yes

Now your database will reflect the latest changes to the entities and contain a new table of comments.

The note

You will also see a new table in the database called migration_versions . It stores the version numbers of the migrations so there is a command with which you can find out which version of the database is currently.


The board

Doctrine 2 Migrations are a great way to update a database on production, because the changes can be made programmatically. This means that we can integrate this task into the project deployment scenario, so the database will update automatically when a new version of the application is deployed. Doctrine 2 migrations also allow you to roll back the changes, since each migration has an up and down method. To roll back to the previous version, you need to specify the version number to which you would like to return, you can do it, as shown below.
 $ php app/console doctrine:migrations:migrate 20110806183439 


Data patterns



Now that we have a Comment entity, let's add Data Fixtures. This is a good moment, at the time when you are creating an entity. We know that the comment must have a connection with the Blog entity, as we have indicated in the metadata, so when creating fixtures for the Comment entities, we will need to specify the Blog entity. We already created fixtures for the Blog entity, so we could just update this file to add Comment entities. This can be managed now, but what happens when we later add users and other entities to our bundle? It would be best to create a new file for fixing the Comment entity. The problem with this approach is how we get access to Blog entries from blog fixtures.

Fortunately, this can be easily achieved by adding a reference to objects in the fixture file to which other fixture files have access. Update the Data Entry Ficures of the Blog Entity located src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
as follows
 <?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; } } 


The changes that are worth noting here are the extension of the AbstractFixture class and the implementation of the OrderedFixtureInterface. Also pay attention to the 2 new use operators importing these classes.

We add links to blog entities using the addReference () method. This first parameter is the link identifier that we can use to retrieve the object later. Finally, we must implement the getOrder () method to specify the order of loading the fixtures. Entries must be uploaded before comments so we return 1.

Fixtures Comment



Now we are ready to define the fixtures for the Comment entity. Create the src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php
and paste
following
 <?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; } } 



With the changes we made in the BlogFixtures class, the CommentFixtures class also extends the AbstractFixture class and implements the OrderedFixtureInterface. This means that we must also implement the getOrder () method. This time we return the value 2, thus providing the fixtures after the fixtures of the records.

, Blog .
 $comment->setBlog($manager->merge($this->getReference('blog-2'))); 


 $ php app/console doctrine:fixtures:load 

: yes
Careful, database will be purged. Do you want to continue y/N ? yes


, . CommentRepository , .

Comment


CommentRepository src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php
 <?php // src/Blogger/BlogBundle/Entity/Repository/CommentRepository.php namespace Blogger\BlogBundle\Entity\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(); } } 


, . where . where , setParameter(). ,
->where('c.blog = ' . blogId)
$blogId SQL injection.

Blog


showAction Blog . Blog 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 )); } 


CommentRepository . $comments .

Blog show


Blog show . Blog show, , , , . . Blog show 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 %} 


Twig include . BloggerBlogBundle:Comment:index.html.twig . . Comment .

Comment show


BloggerBlogBundle:Comment:index.html.twig , . , , .
src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig
 {# 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 %} 



, Comment . Twig, cycle. , , . loop.index0 . , 0. , . HTML-ID article . .

Comment show CSS



, CSS, . , src/Blogger/BlogBundle/Resouces/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; } 




assets web, assets .
 $ php app/console assets:install web 



show pages, , http://localhost:8000/2 .




, . blog show. Symfony 2 . , Symfony2, . CommentType Comment.
 $ php app/console generate:doctrine:form BloggerBlogBundle:Comment 


Comment.

hint

, doctrine:generate:form . -.


CommentType src/Blogger/BlogBundle/Form/CommentType.php

View code
 <?php namespace Blogger\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class CommentType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('user') ->add('comment') ->add('approved') ->add('created', 'datetime') ->add('updated', 'datetime') ->add('blog') ; } /** * @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'Blogger\BlogBundle\Entity\Comment' )); } } 



, , Enquiry Type. , .

.


, blog show, showAction Blog show. , . , , .

Route


. , src/Blogger/BlogBundle/Resources/config/routing.yml
 BloggerBlogBundle_comment_create: path: /comment/{blog_id} defaults: { _controller: "BloggerBlogBundle:Comment:create" } requirements: methods: POST blog_id: \d+ 



, CommentControler . , src/Blogger/BlogBundle/Controller/CommentController.php
 <?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; use Symfony\Component\HttpFoundation\Request; /** * 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(CommentType::class, $comment); return $this->render('BloggerBlogBundle:Comment:form.html.twig', array( 'comment' => $comment, 'form' => $form->createView() )); } public function createAction(Request $request, $blog_id) { $blog = $this->getBlog($blog_id); $comment = new Comment(); $comment->setBlog($blog); $form = $this->createForm(CommentType::class, $comment); $form->handleRequest($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() ->getManager(); $blog = $em->getRepository('BloggerBlogBundle:Blog')->find($blog_id); if (!$blog) { throw $this->createNotFoundException('Unable to find Blog post.'); } return $blog; } } 



2 Comment, new create. new , create . , , , . , , , Comment.



, user comment . , . Comment src/Blogger/BlogBundle/Entity/Comment.php
View code
 <?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' ))); } // .. } 


user comment. . ClassMetadata NotBlank, .



2 new create . , src/Blogger/BlogBundle/Resources/views/Comment/form.html.twig
 {# src/Blogger/BlogBundle/Resources/views/Comment/form.html.twig #} {{ form_start(form, { 'action': path('BloggerBlogBundle_comment_create' , { 'blog_id' : comment.blog.id }), 'method': 'POST', 'attr': {'class': 'blogger'} }) }} {{ form_widget(form) }} <p> <input type="submit" value="Submit"> </p> 



, . , action POST , BloggerBlogBundle_comment_create .

create . , src/Blogger/BlogBundle/Resources/views/Comment/create.html.twig
 {% 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 %} 



createAction Comment , , . BloggerBlogBundle:Comment:form.html.twig .

blog show . src/Blogger/BlogBundle/Resources/views/Blog/show.html
 {# src/Blogger/BlogBundle/Resources/views/Blog/show.html.twig #} {# .. #} {% block body %} {# .. #} <section class="comments" id="comments"> {# .. #} <h3>Add Comment</h3> {{ render(controller('BloggerBlogBundle:Comment:new',{ 'blog_id': blog.id })) }} </section> {% endblock %} 


Twig, render. . BloggerBlogBundle:Comment:new

, http://localhost:8000/2 , .


BloggerBlogBundle:Blog:show.html.twig. 23 BloggerBlogBundle:Blog:show.html.twig , , BloggerBlogBundle:Comment:create .

 {{ render(controller('BloggerBlogBundle:Comment:new',{ 'blog_id': blog.id })) }} 


, .
, , __toString () , . , , , select ( ). , ? , , Twig {{form_widget(form)}}. . CommentType. , FormBuilder. , blog.

, , , FormBuilder , . Comment Blog, FormBuilder , choice , , . choice Symfony 2. __toString() Blog.

 // src/Blogger/BlogBundle/Entity/Blog.php public function __toString() { return $this->getTitle(); } 


hint

Symfony2 , . , . , , , .


, , . , approved, created, updated blog. , CommentType .

hint

, . text, textarea, 2 DateTime , .

- FormBuilder , . , . Comment, FormBuilder .


, src/Blogger/BlogBundle/Form/CommentType.php , ,
 <?php namespace Blogger\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class CommentType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('user'); $builder->add('comment'); } /** * @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'Blogger\BlogBundle\Entity\Comment' )); } public function getBlockPrefix() { return 'blogger_blogbundle_commenttype'; } } 


, . , . , , . ? , . createAction .
 // src/Blogger/BlogBundle/Controller/CommentController.php public function createAction(Request $request, $blog_id) { //.. if ($form->isValid()) { $em = $this->getDoctrine() ->getManager(); $em->persist($comment); $em->flush(); return $this->redirect($this->generateUrl('BloggerBlogBundle_blog_show', array( 'id' => $comment->getBlog()->getId())) . '#comment-' . $comment->getId() ); } //.. } 

Comment persist() flush() . , PHP-, Doctrine 2 . .

.



Conclusion



. . , . , , Doctrine 2 .

(sidebar), . Twig . Assetic , assets.

:

https://symfony.com/
http://tutorial.symblog.co.uk/
http://twig.sensiolabs.org/
http://www.doctrine-project.org/
http://odiszapc.ru/doctrine/

Post Scriptum
, , , .


Part 1 - Configuring Symfony2 and Templates
Part 2 - Contact page: validators, forms, and email
Part 3 - Doctrine 2 and Data Fixtures
Part 5 - Twig Extensions, Sidebar (sidebar) and Assetic
Part 6 - Modular and Functional Testing


, . Thank.

Source: https://habr.com/ru/post/302602/


All Articles