📜 ⬆️ ⬇️

Creating a blog on symfony 2.8 lts [Part 3]






Overview


')
Doctrine 2 is a good example of the object-relational mapping (ORM) mechanism for PHP 5.3+, which allows you to work with the database as transparently as possible, using ordinary PHP objects as an intermediate layer. The basis is a very powerful layer of abstraction from the database (DBAL). The main task of ORM is to link two concepts: PHP objects and records in a relational database. One of the key features of Doctrine is the ability to write queries in its own object-oriented language, somewhat resembling SQL, called Doctrine Query Language (DQL). In addition to small differences from SQL, it allows you to significantly enhance the degree of abstraction between objects and database rows, which allows you to create powerful and flexible queries, while maintaining integrity.

In addition to Doctrine 2, we will also familiarize yourself with the concept of Data Fixture. Data fictures are a mechanism for populating databases for developing and testing suitable test data. At the end of this chapter, you will define a blog model, update the database to reflect the new model, and create several data fixtures.

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)

Doctrine 2: Model


In order for our blog to function, we need to think about how we will store the data. Doctrine 2 provides the ORM library specifically for this purpose. It allows you to use various DBMS including MySQL , PostgreSQL and SQLite . We will use MySQL , but it can easily be replaced with another DBMS.
The note

If you are not familiar with ORM, we will reveal the basic principles. The definition from Wikipedia reads:
ORM (English Object-Relational Mapping, Russian object-relational mapping) is a programming technology that links databases with the concepts of object-oriented programming languages, creating a "virtual object database."

ORM facilitates the transfer of data from a relational database, such as MySQL to PHP objects, which we can manipulate. This allows us to encapsulate the necessary functionality that we need in the table through a class. Provide a user table, it probably has fields: username , password , first_name , last_name , email and job . With ORM, this becomes a class with username , password , first_name , etc. properties, which allows us to call methods such as getUsername () and setPassword () . ORM went much further, it also allows you to get related tables, while we retrieve the user object or later. Now suppose that our user has several friends associated with him. This will be the friends table, with the primary key for the user table. Now, using ORM, we can call the $ user-> getFriends () method to return objects from the friends table. ORM is also involved in saving data, so that we can create objects in PHP, call the save () method and ORM will save the data to the database. Since we use the Doctrine 2 ORM library, you will learn much more about its capabilities as you go through the tutorial.


Note

While this guide will use the Doctrine 2 ORM library, you can select the Doctrine 2 ODM library. There are several variations of this library, including implementations for MongoDB and CouchDB . See the Doctrine Projects page for more information.
You can also see this article , which explains how to install ODM with symfony 2.


Database



Database creation

If you are going through the manual from the first part, you should already have the database parameters specified. If you missed the first part, then update the database_ * values ​​in the parameters file located in app/config/parameters.yml

Create a database using the Doctrine 2 command. This command only creates a database and does not create any tables in the database. If a database with the same name already exists, an error message will be displayed, and the existing database will be left unchanged.
 $ php app/console doctrine:database:create 


Essence Blog



We'll start by creating the Blog Entity class. We already talked about the entities in the previous section when we created the entity Inquiry. Since an entity is intended to store data, it makes sense to use a single entity to present a blog entry. When we define an entity, we do not say that the data will automatically be mapped to the base. We saw this in our Inquiry Entity where the data actually stored was just sent by email to the webmaster.
Create a new src/Blogger/BlogBundle/Entity/Blog.php and paste in the following:
 <?php // src/Blogger/BlogBundle/Entity/Blog.php namespace Blogger\BlogBundle\Entity; class Blog { protected $title; protected $author; protected $blog; protected $image; protected $tags; protected $comments; protected $created; protected $updated; } 


As you can see, this is a simple PHP class. It does not expand parent and has no access methods. Each property is declared as protected so we are not able to access them when working with an object of this class. We could prescribe getters and setters for these properties, but in Doctrine 2 there is a team that performs this task for us.
Before running this command, we need to tell Doctrine 2 how the blog entity should be displayed in the database. The information is listed as metadata using Doctrine 2 mappings. Metadata can be defined in various formats including: YAML , PHP , XML and Annotations . We will use annotations in this tutorial. It is important to note that not all properties in the object should be saved, so we will not provide metadata for them. This gives us the opportunity to select only those elements that we require in order for Doctrine 2 to map them to the database. Replace the content of the Blog entity located at src/Blogger/BlogBundle/Entity/Blog.php
as follows:
 <?php // src/Blogger/BlogBundle/Entity/Blog.php namespace Blogger\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="blog") */ class Blog { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string") */ protected $title; /** * @ORM\Column(type="string", length=100) */ protected $author; /** * @ORM\Column(type="text") */ protected $blog; /** * @ORM\Column(type="string", length=20) */ protected $image; /** * @ORM\Column(type="text") */ protected $tags; protected $comments; /** * @ORM\Column(type="datetime") */ protected $created; /** * @ORM\Column(type="datetime") */ protected $updated; } 


First, we import and define the Doctrine 2 ORM Mappings namespace. This allows us to use annotations to describe entity metadata. Metadata contains information about how properties should be displayed in the database.

The note

We used only a small fraction of the 2 mapping types provided by Doctrine. A complete list of mapping types can be found on the Doctrine 2 website . Other types of comparisons will be discussed later in this guide.


Looking more carefully, you may have noticed that the $ comments property does not describe metadata. This is because we do not need to save, it will simply provide a set of comments related to the blog post. Think about it abstracting from the database. The following code will show more clearly.
 // Create a blog object. $blog = new Blog(); $blog->setTitle("symblog - A Symfony2 Tutorial"); $blog->setAuthor("dsyph3r"); $blog->setBlog("symblog is a fully featured blogging website ..."); // Create a comment and add it to our blog $comment = new Comment(); $comment->setComment("Symfony2 rocks!"); $blog->addComment($comment); 

The above code snippet demonstrates the normal behavior you expect from a blog and class comments. The internal $ blog-> addComment () method can be implemented as follows.
 class Blog { protected $comments = array(); public function addComment(Comment $comment) { $this->comments[] = $comment; } } 

The addComment method simply adds a new comment object to the blog property $ comments . Getting comments is also fairly simple.
 class Blog { protected $comments = array(); public function getComments() { return $this->comments; } } 

As you can see, the $ comments property is just a list of Comment objects. Doctrine 2 does not change how it works. Doctrine 2 will be able to automatically populate the $ comments property with objects associated with the blog object.
Now that we’ve talked about how to match entity properties in Doctrine 2, we can generate access methods using the following command in the console.
 $ php app/console doctrine:generate:entities Blogger 

You will be notified that the Blog Entity has been updated with access methods. Every time we make changes to the ORM metadata of our entity classes, we can run this command to generate any additional access methods. This command will not make changes to access methods already present in the entity, so your existing accesses will never be overridden by this command. This is important because you can later change some of the methods.

The note

While we use annotations in our entity, you can convert the mapping information to other supported mapping formats using the doctrine: mapping: convert command. For example, the following command converts the entity mappings above to the yml format.

 $ php app/console doctrine:mapping:convert --namespace="Blogger\BlogBundle\Entity\Blog" yaml src/Blogger/BlogBundle/Resources/config/doctrine 


And it will create a file in src/Blogger/BlogBundle/Resources/config/doctrine/Blogger.BlogBundle.Entity.Blog.orm.yml which will contain the blog entity mappings in the yml format.


Now we are ready to create a Blog entity view in the database. There are 2 ways we can achieve this. We can use Doctrine 2 schema commands to update the database or more powerful Doctrine 2 migrations. Now we will use the schema commands. Doctrine migrations will be covered in the next section of this guide.

Creating a blog table



To create a blog table, we can use the following command.

 $ php app/console doctrine:schema:create 


This will trigger actions to generate a database schema for the blog entity. You can also pass the --dump-sql option to dump the SQL. If you look at the database, you will see that the blog table was created with the fields to which we mapped.

The board

We used a number of Symfony2 console commands. You can get help on any command by entering the option --help. For example, to see help for the doctrine: schema: create command type:

 $ php app/console doctrine:schema:create --help 


Background information will display usage methods as well as available options. Most commands are executed with several options that can extend it.


Model and display. Write a blog entry.



Now we have the essence of Blog and an updated database for its presentation, we can begin to integrate the model into the display. We will start by creating the show page of our blog.

Show Blog Route

We'll start by creating a route for blog show action. The blog will identify the post by unique ID, so this ID must be presented in the URL. Update the BloggerBlogBundle route, located src/Blogger/BlogBundle/Resources/config/routing.yml following:

 # src/Blogger/BlogBundle/Resources/config/routing.yml BloggerBlogBundle_blog_show: path: /{id} defaults: { _controller: "BloggerBlogBundle:Blog:show" } requirements: methods: GET id: \d+ 


Since the post ID must be in the URL, we defined the id of the placeholder. This means that URLs like http: // localhost: 8000/1 and http: // localhost: 8000 / my-blog will correspond to this route. However, we know that the blog id should be an integer (it is defined this way in the entity mapping) so we can add a constraint that determines that this route will only match if the id parameter contains an integer. This is achieved using the id: \ d + route requirement. Now only the first example URL will match, but http: // localhost: 8000 / my-blog is gone. You can also see how the appropriate route will perform the showAction, Blogger \ BlogBundle \ Blog controller method. This controller has yet to be created.

ShowAction method



Glue connecting Model and display - this is the controller, this is the place where we will begin to create the page. We could add the showAction method to our existing Page controller, but since this page is dedicated to displaying blog entities, it would be better to add them to our own Blog controller.
Create a new file located in src/Blogger/BlogBundle/Controller/BlogController.php and paste the following.
 <?php // src/Blogger/BlogBundle/Controller/BlogController.php namespace Blogger\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; /** * Blog controller. */ class BlogController extends Controller { /** * Show a blog entry */ public function showAction($id) { $em = $this->getDoctrine()->getManager(); $blog = $em->getRepository('BloggerBlogBundle:Blog')->find($id); if (!$blog) { throw $this->createNotFoundException('Unable to find Blog post.'); } return $this->render('BloggerBlogBundle:Blog:show.html.twig', array( 'blog' => $blog, )); } } 

We created a new controller for the Blog entity and defined the showAction method, and also set the id parameter in the BloggerBlogBundle_blog_show route rule, it will be inserted as an argument to the showAction method. If we defined more parameters in the route rule, they would also be inserted as arguments, separated by commas.
Next, we need to retrieve the Blog Entity from the database. We first use another helper method of the Symfony \ Bundle \ FrameworkBundle \ Controller \ Controller class to get the Doctrine2 Manager. The job of the Manager is to process, extract and save objects to and from the database. Next, we use the Manager object to get the Doctrine 2 Repository for the BloggerBlogBundle: Blog entity. The syntax shown here is just abbreviations that can be used in Doctrine 2 instead of indicating the full name of the Blogger \ BlogBundle \ Entity \ Blog entity. Together with the repository object, we call the find () method and pass in the $ id argument. This method will receive an object by its primary key.
And at the end we check that the entity was found and pass it to the display. If the entity is not found, then a createNotFoundException will be returned. This will form a 404 Not Found answer.

The note

The repository object provides access to a number of useful helper methods, including
 // Return entities where 'author' matches 'dsyph3r' $em->getRepository('BloggerBlogBundle:Blog')->findBy(array('author' => 'dsyph3r')); // Return one entity where 'slug' matches 'symblog-tutorial' $em->getRepository('BloggerBlogBundle:Blog')->findOneBySlug('symblog-tutorial'); 


We will create our own custom classes in the repository in the next part, when we need more complex database queries.

Display



We created the showAction method for the Blog controller and can focus on the display of the Blog entity. As indicated in the showAction method, the BloggerBlogBundle template will be displayed : Blog: show.html.twig . Let's create this src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig and insert the following.
 {# src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig #} {% extends 'BloggerBlogBundle::layout.html.twig' %} {% block title %}{{ blog.title }}{% endblock %} {% block body %} <article class="blog"> <header> <div class="date"><time datetime="{{ blog.created|date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div> <h2>{{ blog.title }}</h2> </header> <img src="{{ asset(['images/', blog.image]|join) }}" alt="{{ blog.title }} image not found" class="large" /> <div> <p>{{ blog.blog }}</p> </div> </article> {% endblock %} 

As you might expect, we will start by extending the main Blogger BlogBundle main template. Next, we will override the page title for our blog. This will be useful for SEO since the title of the blog page is more descriptive than the default title. Finally, we will override the body block to display the content of the Blog entity. We use the asset function again to display the image in a blog. Blog images should be placed in the web / images directory. Download images can be on the link .

CSS



In order to make a visual design, we need to add some styles. Add styles below to src/Blogger/BlogBundle/Resouces/public/css/blog.css.
src/Blogger/BlogBundle/Resouces/public/css/blog.css.

 .date { margin-bottom: 20px; border-bottom: 1px solid #ccc; font-size: 24px; color: #666; line-height: 30px } .blog { margin-bottom: 20px; } .blog img { width: 190px; float: left; padding: 5px; border: 1px solid #ccc; margin: 0 10px 10px 0; } .blog .meta { clear: left; margin-bottom: 20px; } .blog .snippet p.continue { margin-bottom: 0; text-align: right; } .blog .meta { font-style: italic; font-size: 12px; color: #666; } .blog .meta p { margin-bottom: 5px; line-height: 1.2em; } .blog img.large { width: 300px; min-height: 165px; } 


The note

If you do not use the symbolic link method to access the bundle’s assets in the web folder, you must re-run the assets installation command.
 $ php app/console assets:install web 


Since we created the controller and the display for the showAction method, let's take a look at the page. Enter http: // localhost: 8000/1 into your browser. This is not the page you expected to see?



Symfony generated a 404 Not Found response. This happened because we have no data in the database, i.e. An entity with an id of 1 cannot be found. You can simply insert a row into the blog table of your database, but we will use the method much better: Data Fixtures .

Data patterns



We can use fixtures to populate a database with some simple test data. For this we use doctrine-fixtures-bundle and data-fixtures. The Doctrine Fixtures extension does not ship with symfony2, we have to manually install it. Fortunately, this is an easy task. Open the composer.json file located in the project root and insert the following:
 "require": { // ... "doctrine/doctrine-fixtures-bundle": "dev-master", "doctrine/data-fixtures" : "dev-master" } 

Next update the libraries with the command.
 $ composer update 


This will update all the libraries from Github and install them in the necessary directories.
Now let's register the DoctrineFixturesBundle in the kernel located in
app/AppKernel.php

 // app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), // ... ); // ... } 

Blog Fiction



Now we are ready to define fixtures for our blog. Create the src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php and paste
following:
 <?php // src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php namespace Blogger\BlogBundle\DataFixtures\ORM; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Common\Persistence\ObjectManager; use Blogger\BlogBundle\Entity\Blog; class BlogFixtures implements FixtureInterface { public function load(ObjectManager $manager) { $blog1 = new Blog(); $blog1->setTitle('A day with Symfony2'); $blog1->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing eletra electrify denim vel ports.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut velocity magna. Etiam vehicula nunc non leo hendrerit commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra. Cras el mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra. Cras elementum molestie vestibulum. Morbi id quam nisl. Praesent hendrerit, orci sed elementum lobortis, justo mauris lacinia libero, non facilisis purus ipsum non mi. Aliquam sollicitudin, augue id vestibulum iaculis, sem lectus convallis nunc, vel scelerisque lorem tortor ac nunc. Donec pharetra eleifend enim vel porta.'); $blog1->setImage('beach.jpg'); $blog1->setAuthor('dsyph3r'); $blog1->setTags('symfony2, php, paradise, symblog'); $blog1->setCreated(new \DateTime()); $blog1->setUpdated($blog1->getCreated()); $manager->persist($blog1); $blog2 = new Blog(); $blog2->setTitle('The pool on the roof must have a leak'); $blog2->setBlog('Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Na. Cras elementum molestie vestibulum. Morbi id quam nisl. Praesent hendrerit, orci sed elementum lobortis.'); $blog2->setImage('pool_leak.jpg'); $blog2->setAuthor('Zero Cool'); $blog2->setTags('pool, leaky, hacked, movie, hacking, symblog'); $blog2->setCreated(new \DateTime("2011-07-23 06:12:33")); $blog2->setUpdated($blog2->getCreated()); $manager->persist($blog2); $blog3 = new Blog(); $blog3->setTitle('Misdirection. What the eyes see and the ears hear, the mind believes'); $blog3->setBlog('Lorem ipsumvehicula nunc non leo hendrerit commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque.'); $blog3->setImage('misdirection.jpg'); $blog3->setAuthor('Gabriel'); $blog3->setTags('misdirection, magic, movie, hacking, symblog'); $blog3->setCreated(new \DateTime("2011-07-16 16:14:06")); $blog3->setUpdated($blog3->getCreated()); $manager->persist($blog3); $blog4 = new Blog(); $blog4->setTitle('The grid - A digital frontier'); $blog4->setBlog('Lorem commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra.'); $blog4->setImage('the_grid.jpg'); $blog4->setAuthor('Kevin Flynn'); $blog4->setTags('grid, daftpunk, movie, symblog'); $blog4->setCreated(new \DateTime("2011-06-02 18:54:12")); $blog4->setUpdated($blog4->getCreated()); $manager->persist($blog4); $blog5 = new Blog(); $blog5->setTitle('You\'re either a one or a zero. Alive or dead'); $blog5->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing elittibulum vulputate mauris eget erat congue dapibus imperdiet justo scelerisque.'); $blog5->setImage('one_or_zero.jpg'); $blog5->setAuthor('Gary Winston'); $blog5->setTags('binary, one, zero, alive, dead, !trusting, movie, symblog'); $blog5->setCreated(new \DateTime("2011-04-25 15:34:18")); $blog5->setUpdated($blog5->getCreated()); $manager->persist($blog5); $manager->flush(); } } 




The fixture file demonstrates a number of important features when using Doctrine 2, including how to save objects to the database.
Let's see how we create one blog entry.

 $blog1 = new Blog(); $blog1->setTitle('A day in paradise - A day with Symfony2'); $blog1->setBlog('Lorem ipsum dolor sit d us imperdiet justo scelerisque. Nulla consectetur...'); $blog1->setImage('beach.jpg'); $blog1->setAuthor('dsyph3r'); $blog1->setTags('symfony2, php, paradise, symblog'); $blog1->setCreated(new \DateTime()); $blog1->setUpdated($this->getCreated()); $manager->persist($blog1); // .. $manager->flush(); 

We start by creating a Blog object and defining its property values. At the moment, Doctrine 2 does not know anything about the entity object. This will happen only when we call $ manager-> persist ($ blog1) , with this we instruct Doctrine 2 to start managing this entity object. The $ manager object here is an instance of the Manager object that we saw earlier when retrieving objects from the database. It is important to note that while Doctrine 2 is currently aware of an entity object, it is still not stored in the database. To do this, you need to call $ manager-> flush (). The flush method makes Doctrine 2 begin to interact with the database and use all the entities it manages. For best performance, you should group the Doctrine 2 commands and perform all actions at once. We created each object, asked Doctrine 2 to start managing them, and then performed the necessary operations.

Fixture loading



Now we are ready to load fixtures into the database.
 $ php app/console doctrine:fixtures:load 


To the question whether to continue the execution of the command, we answer: yes

If we take a look at http: // localhost: 8000/1, we will see a blog entry.



Try changing the id in the URL to 2. You should see the following entry.

If you go to the URL http: // localhost: 8000/100 you will see a 404 Not Found error. This happened, as expected, since there is no Blog entity with an id of 100. Now try typing http: // localhost: 8000 / symfony2-blog Why is there no 404 Not Found error? This is due to the fact that the showAction method was not called. The URL does not match any routes in the application due to the \ d + requirement that we set in the BloggerBlogBundle_blog_show route. That is why you see no route found for the exception "GET / symfony2-blog".

Time stamp



Finally, in this part, we look at the 2 properties of timestamps in essence Blog: created and updated. The functionality for these 2 members is commonly referred to as Timestampable behavior. These properties contain the time when the record was created and the time it was last updated. Since we don’t want to manually set these fields every time we create or update a record, we can use Doctrine 2 for this purpose.
Doctrine 2 comes with an Event System that provides Lifecycle Callbacks. We can use these callback events to register our entities, receive notifications of events during the entire lifetime of the object.A few examples of events that we can be notified of before an update occurs, after being saved and deleted. To use Lifecycle Callbacks in our entity, we need to register an entity for them. This is done using metadata, in essence. Update Blog Entitysrc/Blogger/BlogBundle/Entity/Blog.php
 <?php // src/Blogger/BlogBundle/Entity/Blog.php // .. /** * @ORM\Entity * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks */ class Blog { // .. } 

Now, let's add a method to the Blog entity that logs the PreUpdate event. We will also add a constructor to set default values ​​for the created and updated properties.

 <?php // src/Blogger/BlogBundle/Entity/Blog.php // .. /** * @ORM\Entity * @ORM\Table(name="blog") * @ORM\HasLifecycleCallbacks */ class Blog { // .. public function __construct() { $this->setCreated(new \DateTime()); $this->setUpdated(new \DateTime()); } /** * @ORM\PreUpdate */ public function setUpdatedValue() { $this->setUpdated(new \DateTime()); } // .. } 

We registered the Blog entity to be notified of the preUpdate event to set the updated property value. Now, when you remove the fixtures from the table, and also remove from each object

 setCreated(); setUpdated(); 


in the file and rerun the task of loading the fixtures, you will see that the created and updated properties are set automatically.
src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php




The note

timestampable , . StofDoctrineExtensionsBundle Doctrine 2 , Timestampable, Sluggable Sortable.
. .


Conclusion



We looked at a number of concepts for working with models, in Doctrine 2. We considered Data Fixtures, which provided us with an easy way to get test data for developing and testing our application.
Next we look at the extension of the model, adding an entity for comments. We will start creating the home page and apply the user repository for this. We will also introduce the concept of Doctrine Migrations and how forms interact with Doctrine 2 so that we can post comments on the blog.

Sources and supporting materials:

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

Post Scriptum
Thank you all for the attention and comments made on the project, if you have any difficulties or questions, unsubscribe in the comments or private messages, add to friends.


Part 1 - Configuring Symfony2 and Templates
Part 2 - Contact page: validators, forms, and email
Part 4 - Comment Model, Repository and Migrations of Doctrine 2
Part 5 - Twig Extensions, Sidebar (sidebar) and Assetic
Part 6 - Modular and Functional Testing


Also, if you like the guide, you can put a star to the project repository or subscribe. Thank.

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


All Articles