Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Fetching Relations

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Yes! Each Article is now related to two comments in the database. So, on the article show page, it's time to get rid of this hardcoded stuff and, finally, query for the true comments for this Article.

In src/Controller, open ArticleController and find the show() action:

... lines 1 - 16
class ArticleController extends AbstractController
{
... lines 19 - 40
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack)
{
if ($article->getSlug() === 'khaaaaaan') {
$slack->sendMessage('Kahn', 'Ah, Kirk, my old friend...');
}
$comments = [
'I ate a normal rock once. It did NOT taste like bacon!',
'Woohoo! I\'m going on an all-asteroid diet!',
'I like bacon too! Buy some from my site! bakinsomebacon.com',
];
return $this->render('article/show.html.twig', [
'article' => $article,
'comments' => $comments,
]);
}
... lines 61 - 73
}

This renders a single article. So, how can we find all of the comments related to this article? Well, we already know one way to do this.

Remember: whenever you need to run a query, step one is to get that entity's repository. And, surprise! When we generated the Comment class, the make:entity command also gave us a new CommentRepository:

... lines 1 - 2
namespace App\Repository;
use App\Entity\Comment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method Comment|null find($id, $lockMode = null, $lockVersion = null)
* @method Comment|null findOneBy(array $criteria, array $orderBy = null)
* @method Comment[] findAll()
* @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class CommentRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Comment::class);
}
... lines 21 - 49
}

Thanks MakerBundle!

Get the repository by adding a CommentRepository argument. Then, let's see, could we use one of the built-in methods? Try $comments = $commentRepository->findBy(), and pass this article set to the entire $article object:

... lines 1 - 6
use App\Repository\CommentRepository;
... lines 8 - 17
class ArticleController extends AbstractController
{
... lines 20 - 41
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack, CommentRepository $commentRepository)
{
if ($article->getSlug() === 'khaaaaaan') {
... line 48
}
$comments = $commentRepository->findBy(['article' => $article]);
... lines 52 - 63
}
... lines 65 - 77
}

Dump these comments and die:

... lines 1 - 17
class ArticleController extends AbstractController
{
... lines 20 - 41
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack, CommentRepository $commentRepository)
{
... lines 47 - 50
$comments = $commentRepository->findBy(['article' => $article]);
dump($comments);die;
... lines 53 - 63
}
... lines 65 - 77
}

Then, find your browser and, try it! Yes! It returns the two Comment objects related to this Article!

So, the weird thing is that, once again, you need to stop thinking about the columns in your tables, like article_id, and only think about the properties on your entity classes. That's why we use 'article' => $article. Of course, behind the scenes, Doctrine will make a query where article_id = the id from this Article. But, in PHP, we think all about objects.

Fetching Comments Directly from Article

As nice as this was... there is a much simpler way! When we generated the relationship, it asked us if we wanted to add an optional comments property to the Article class, for convenience. We said yes! And thanks to that, we can literally say $comments = $article->getComments(). Dump $comments again:

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 37
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack)
{
... lines 43 - 46
$comments = $article->getComments();
dump($comments);die;
... lines 49 - 59
}
... lines 61 - 73
}

Oh, and now, we don't need the CommentRepository anymore:

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 37
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack)
{
... lines 43 - 59
}
... lines 61 - 73
}

Cool.

Lazy Loading

Head back to your browser and, refresh! It's the exact same as before. Wait, what? What's this weird PersistentCollection thing?

Here's what's going on. When Symfony queries for the Article, it only fetches the Article data: it does not automatically fetch the related Comments. And, for performance, that's great! We may not even need the comment data! But, as soon as we call getComments() and start using that, Doctrine makes a query in the background to go get the comment data.

This is called "lazy loading": related data is not queried for until, and unless, we use it. To make this magic possible, Doctrine uses this PersistentCollection object. This is not something you need to think or worry about: this object looks and acts like an array.

To prove it, let's foreach over $comments as $comment and dump each $comment inside. Put a die at the end:

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 37
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack)
{
... lines 43 - 46
$comments = $article->getComments();
foreach ($comments as $comment) {
dump($comment);
}
die;
... lines 52 - 62
}
... lines 64 - 76
}

Try it again! Boom! Two Comment objects!

Fetching the Comments in the Template

Back in the controller, we no longer need these hard-coded comments. In fact, we don't even need to pass comments into the template at all! That's because we can call the getComments() method directly from Twig!

Remove all of the comment logic:

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 37
/**
* @Route("/news/{slug}", name="article_show")
*/
public function show(Article $article, SlackClient $slack)
{
if ($article->getSlug() === 'khaaaaaan') {
$slack->sendMessage('Kahn', 'Ah, Kirk, my old friend...');
}
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
... lines 51 - 63
}

And then, jump into templates/article/show.html.twig. Scroll down a little... ah, yes! First, update the count: article.comments|length:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
... lines 11 - 39
<div class="row">
<div class="col-sm-12">
<h3><i class="pr-3 fa fa-comment"></i>{{ article.comments|length }} Comments</h3>
... lines 43 - 71
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
... lines 80 - 86

Easy! Then, below, change the loop to use for comment in article.comments:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
... lines 11 - 39
<div class="row">
<div class="col-sm-12">
<h3><i class="pr-3 fa fa-comment"></i>{{ article.comments|length }} Comments</h3>
... lines 43 - 57
{% for comment in article.comments %}
... lines 59 - 69
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
... lines 80 - 86

And because each comment has a dynamic author, print that with {{ comment.authorName }}. And the content is now comment.content:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
... lines 11 - 39
<div class="row">
<div class="col-sm-12">
<h3><i class="pr-3 fa fa-comment"></i>{{ article.comments|length }} Comments</h3>
... lines 43 - 57
{% for comment in article.comments %}
<div class="row">
<div class="col-sm-12">
... line 61
<div class="comment-container d-inline-block pl-3 align-top">
<span class="commenter-name">{{ comment.authorName }}</span>
... lines 64 - 65
<span class="comment"> {{ comment.content }}</span>
... line 67
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
... lines 81 - 87

Oh, and, because each comment has a createdAt, let's print that too, with our trusty ago filter:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
... lines 11 - 39
<div class="row">
<div class="col-sm-12">
<h3><i class="pr-3 fa fa-comment"></i>{{ article.comments|length }} Comments</h3>
... lines 43 - 57
{% for comment in article.comments %}
<div class="row">
<div class="col-sm-12">
... line 61
<div class="comment-container d-inline-block pl-3 align-top">
<span class="commenter-name">{{ comment.authorName }}</span>
<small>about {{ comment.createdAt|ago }}</small>
... line 65
<span class="comment"> {{ comment.content }}</span>
... line 67
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
... lines 81 - 87

Love it! Let's try it! Go back, refresh and... yes! Two comments, from about 17 minutes ago. And, check this out: on the web debug toolbar, you can see that there are two database queries. The first query selects the article data only. And the second selects all of the comment data where article_id matches this article's id - 112. This second query doesn't actually happen until we reference the comments from inside of Twig:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="show-article-container p-3 mt-4">
... lines 11 - 39
<div class="row">
<div class="col-sm-12">
<h3><i class="pr-3 fa fa-comment"></i>{{ article.comments|length }} Comments</h3>
... lines 43 - 57
{% for comment in article.comments %}
... lines 59 - 70
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
... lines 81 - 87

That laziness is a key feature of Doctrine relations.

Next, it's time to talk about the subtle, but super-important distinction between the owning and inverse sides of a relation.

Leave a comment!

44
Login or Register to join the conversation
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

Offtopic: I want to chain objects. I got OneToMany relations.
I want to go from Object1->Object2->Object3.
But when I dd(Object1) it says that Object3 is: +__isInitialized__: false
And other columns of Object3 are null, while in the database they have values.
How do I fix this?

Reply

Hey @Farry

It's because Entities are proxied this allows to delay running sql query and the data will be loaded when you try to access this data. But if you want to see all data you can add fetch="EAGER" to the @ORM\OneToMany() annotation. It may cause performance issues.

Cheers!

1 Reply
Farshad Avatar

So there is no valid way of doing this without risk of performance issues? I don't understand why this problem is caused specificly here, while chaining objects is a normal thing right?
Lets say you want user images and you only have the userID in the url. Then you write something like this:
$user->getPosts()->getImages();
On other places on my website, it works just fine. So I cant understand why I am getting this error on a specific part which is about machines in the factory.
$machine->getProduct()->getControlPlan();
Its not showing the controlplan for some reason.

Reply

oh that is tricky! You can try to make custom queries with QueryBuilder using joins and select all data you need explicitly. Than data you not selected will be queried lazyly, but everything mentioned in ->select() will be abvailable

This will allow you to bypass perrformance issues, but it really depends on you project and amount of data you trying select

Cheers!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

Off topic. I get the following error and I dont understand how to fix it. Last thing I remember I did was updating composer.

Uncaught PHP Exception Doctrine\Common\Annotations\AnnotationException: "[Semantical Error] The annotation "@Gedmo\Mapping\Annotation\Slug" in property App\Entity\Controlplan\CPControlplan::$id was never imported. Did you maybe forget to add a "use" statement for this annotation?" at C:\xampp\htdocs\manapp\manufacturing-application\vendor\doctrine\annotations\lib\Doctrine\Common\Annotations\AnnotationException.php line 39

Reply

Hey @Farry7!

I'm not sure why a composer update would cause this, but on a high level, this error means:

Hey! I see an annotation (e.g. @Slug) but there is no corresponding use statement in that class for it.

However, the fact that it lists the entire annotation class name - Gedmo\Mapping\Annotation\Slug - (and it's correct), leads me to think that the use statement IS there. In that case, it simply can't find this class in your project! Did you somehow uninstall the doctrine-extensions library (this class comes from https://github.com/doctrine-extensions/DoctrineExtensions, which is a dependency of the stof doctrine extensions bundle)?

Cheers!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

I added 'use Timestampable' on my Entity. Then I did make:migration. But it says it's already in sync. I thought it would make a new migration file which adds 'date_added' and 'date_modified' in the table on the database. How do I do this?

Reply

Hey Farry,

Which trait did you use? There is one specific for entities, I believe it's called "TimestampableEntity". Give it a try and let me know

Cheers!

Reply
Farshad Avatar

Thanks, the migration worked. But now when I am adding an input to the database, it gives an error saying that:
"An exception occurred while executing 'INSERT INTO measurement (measurement, created_at, updated_at, measurement_meetstaat_id) VALUES (?, ?, ?, ?)' with params [34, null, null, 125]:"

I also generated Getters and Setters on the Entity. Do I also need to do something else?

Reply

I believe you forgot to enable the doctrine extension. Here you can see how to do it https://symfony.com/doc/cur...

Reply
Farshad Avatar
Farshad Avatar Farshad | MolloKhan | posted 2 years ago | edited

I did it. Now it says the following:
The Doctrine connection "other" referenced in service "stof_doctrine_extensions.listener.timestampable" does not exist. Available connections names: "default".

Do I have to manually add setCreatedAt(), setUpdatedAt() in the controller? I thought the trait would take care of some of it.

my stof_doctrine_extensions.yaml:


stof_doctrine_extensions:
    default_locale: en_US
    orm:
        default:
            sluggable: true
            tree: true
            timestampable: false # not needed: listeners are not enabled by default
        other:
            timestampable: true

EDIT: I just added a $date = new \DateTime(); and passed it through. Now it works. At first, I thought that Symfony might handle some of it in the background.

Reply

The extension bundle should take care of it. It works via Doctrine listeners. The problem is the other connection configuration is just an example, you should put it on the "default" one (unless you're using multiple databases)

Cheers!

Reply

Hey Farry,

It should come from the Controller which rendered the template

Cheers!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

Off topic: I am getting an error: App\Entity\Measurement object not found by the @ParamConverter annotation.
I just cant seem to understand how to fix this.

Reply

Hey Farry,

The ParamConverter grabs the query parameter from the URL and try to fetch an object based on it. You can read more about it here https://symfony.com/doc/cur...

Cheers!

Reply

Hey @Farry7

The "article" string is the name of the class' property

Cheers!

Reply
Joe L. Avatar

So following this I find that at no time have we added the createdAt date in the code supplied! - scratch that, retraced and sorted!!!

Reply

Hey Joe L.

We added the TimestampableEntity trait to the Comment class in the first video, you may have lost it but here it is :)
https://symfonycasts.com/screencast/doctrine-relations/comment-entity?playAt=237

Cheers!

Reply
Joe L. Avatar

Yeah - I retraced my steps and saw I missed one!!!! Thanks anyway!!!

1 Reply
Szymon Z. Avatar
Szymon Z. Avatar Szymon Z. | posted 3 years ago

How to make Lazy Loading if you havent (Article $article) just normal repository? Not everyone take params from route

Reply

Hey Szymon Z.

What you won't is to not fetch for the Article record automatically? If that's the case just change yourArticle $article argument to string $slug

Cheers!

Reply
Stephan Avatar
Stephan Avatar Stephan | posted 3 years ago

Hi,
I did not succeed the dump($comments);die; I have this error:
App\Entity\Article object not found by the @ParamConverter annotation.
Can you help me please?

Reply

Hey Stephansav,

So, Doctrine was not able to find the Article object. Please, make sure that an Article exist with the slug you specified in the URL. For this, you can check your article table in MySQL DB. Most probably you just have a typo in your slug

Cheers!

Reply
Avraham M. Avatar
Avraham M. Avatar Avraham M. | posted 4 years ago

Hello!
About caching relations,
I want to cache entity and some of its relations.

1)
I suppose I need to serialize to json.
Will it be better to cache serialized json objects or other policy?
2)
I can use fetch="EAGER" for relation OneToMany, let's say Patient => Employers.
Then I want to serialize so eager fetched relations objects will be serialized as well.
In my test, I have $patient has 2 employers,
also used group to focus on "firstName" field and "employers" relation only.
$json = $this->m_serializer->serialize(
$patient,
'json', ['groups' => 'demographics']
);

Yet, I get empty relation objects.
{"firstName":"Sarah","employers":[[],[]]}
3)
Is it possible without fetch="EAGER" ?
4)
Trying to create custom normalizer as in
https://symfony.com/doc/cur...
public function normalize($data...)
$data is $patient entity comes with relations yet after
$res = $this->normalizer->normalize($data, $format, $context);
$res is {"firstName":"Sarah","employers":[[],[]]}
with [] instead employer relation array.

Thanks!

Reply

Hey Avraham M.

If you want to cache queries, you can use Doctrine cache to do it but I don't recommend it because you would have to purge the cache whenever your object changes and it can be very problematic. What you can do is to use the app.cache service and cache computed data. What I mean is if you want to cache the serialized version of an object, you can cache the result and probably add a TTL to it, or do whatever you need to be sure that you are not using old cached data

Cheers!

Reply
Avraham M. Avatar

I started to use TagAWareCacheInterface cache, very interesting!
Thanks!

Reply
hanen Avatar

hi everyone
I get empty comments, I don't understand why when Im using the find() methods in Entity Repository I got empty array
Thank you for your help..

Reply

Hey hanen

If you already load the fixtures (or manually added some comments), you should see the comments. So, first double check the database records and if you see comments there, then maybe you are not fetching from the right repository. Double check you are injecting the CommenRepository.

Cheers!

Reply
hanen Avatar

Hi Diego
I already loaded my comments and when I use php bin/console doctrine:query:sql 'SELECT * FROM comment' it show up normally but in my controller when I fetching for comments it doesn't work

ArticleController.php on line 80:
[]
I think my problem is that I migrate comment table it produced following error ::
SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'comment' already exists

Reply

And you are using the CommentRepository in your controller, right? Can you double check that you are on the dev environment? or that you loaded the fixtures for the dev environment
Another thing you can check is the profiler at the "Doctrine" tab, so you can see which queries are being executed

Reply
hanen Avatar

Thank you for your response
I think findBy method is not working but findAll returns records of comments

Reply

Ohh, the findBy() method expects a criteria, in other words, your WHERE clause, so it can filter the records. Probably you where passing something that matches nothing in your table? That's why I recommend you to inspect the executed queries via the profiler :)

Reply
hanen Avatar

Hi Diego
the doctrine connection had the access to database and in symfony profiler there are two queries that were executed

#▼ Time Info
2 1.00 ms
SELECT t0.id AS id_1, t0.author_name AS author_name_2, t0.content AS content_3, t0.created_at AS created_at_4, t0.updated_at AS updated_at_5, t0.article_id AS article_id_6 FROM comment t0 WHERE t0.article_id = ?
Parameters:
[▼
64
]
Hide formatted query Hide runnable query Explain query
SELECT
t0.id AS id_1,
t0.author_name AS author_name_2,
t0.content AS content_3,
t0.created_at AS created_at_4,
t0.updated_at AS updated_at_5,
t0.article_id AS article_id_6
FROM
comment t0
WHERE
t0.article_id = ?
SELECT t0.id AS id_1, t0.author_name AS author_name_2, t0.content AS content_3, t0.created_at AS created_at_4, t0.updated_at AS updated_at_5, t0.article_id AS article_id_6 FROM comment t0 WHERE t0.article_id = 64;
1 4.00 ms
SELECT t0.id AS id_1, t0.title AS title_2, t0.slug AS slug_3, t0.content AS content_4, t0.published_at AS published_at_5, t0.price AS price_6, t0.name AS name_7, t0.author AS author_8, t0.heart_count AS heart_count_9, t0.image_filename AS image_filename_10, t0.created_at AS created_at_11, t0.updated_at AS updated_at_12 FROM article t0 WHERE t0.slug = ? LIMIT 1
Parameters:

2 Reply

Hey hanen, sorry for the late reply

Your queries looks fine to me. The first one is trying to get all comments for given Article ID (64), and the second one is trying to find an Article from its slug field

If you think you have a problem with your DB, you can recreate it from scratch like this:


bin/console doctrine:database:drop --force
bin/console doctrine:database:create
bin/console doctrine:schema:create
bin/console doctrine:fixtures:load

Cheers!

1 Reply
hanen Avatar

Hi Diego
when I run this command php bin/console:schema:create I got this error:
Schema-Tool failed with Error 'An exception occurred while executing 'CREATE TABLE migration_versions (version VARCHAR(255) NOT NULL, PRIMARY KEY
(version)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB':
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes' while executing DDL: CREATE TABL
E migration_versions (version VARCHAR(255) NOT NULL, PRIMARY KEY(version)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = Inno
DB
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes..
And thank you for your assistance :)))

3 Reply

Ohh, that's because of your MySql version, please upgrade to 5.7+ or if that's not possible you will have to limit the length of your fields to 181 characters I believe.

Reply
Dominik Avatar
Dominik Avatar Dominik | posted 4 years ago

Hello.
How can I fetch comments using "eager loading". Lazy loading it's not always best option.

Reply

Hey Dominik!

You have two options for this:

1) You can add a fetch="EAGER" option to your OneToMany annotation. But that will fetch them "eager" in ALL cases.

2 Create a custom query that JOINs to the comments and fetches them. We do that here: https://symfonycasts.com/screencast/doctrine-relations/join-n-plus-one#solving-the-n-1-extra-queries-problem. Well, that is an example of starting with a Comment and joining back to an Article. You would do the opposite: start with an Article, then leftJoin over to comments.

Let me know if this helps!

Cheers!

Reply
Dominik Avatar

I watch that video about join and it works. Thank you.

Reply
Frank K. Avatar
Frank K. Avatar Frank K. | posted 4 years ago

Hello I get a error with:
An exception has been thrown during the rendering of a template ("An exception occurred while executing 'SELECT t0.id AS id_1, t0.author_name AS author_name_2, t0.content AS content_3, t0.created_at AS created_at_4, t0.updated_at AS updated_at_5, t0.article_id AS article_id_6 FROM comment t0 WHERE t0.article_id = ?' with params [49]:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.created_at' in 'field list'"). Please help me out

Reply

Hey Frank K.

I believe your DB schema is out of sync, I mean, you added a new property to your entity but didn't update the schema

php ./bin/console doctrine:schema:update --force```
 pass --dump-sql first so you can see the SQL which will be executed

Cheers!
Reply
Dmitriy Avatar
Dmitriy Avatar Dmitriy | posted 4 years ago

How can I check that the PersistentCollection is empty?

$comments = $article->getComments();

empty($comments)

Always return false.

Reply
Dmitriy Avatar

I find it's

$article->getComments()->isEmpty()

Reply

Hey Dmitriy,

Yeah, you nailed it! That's exactly the best way to check it. The "empty($comments)" always returns false because you check that $comments variable is empty, but that's not true, it's always an object. And thanks for sharing your solution with others!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

The course is built on Symfony 4, but the principles still apply perfectly to Symfony 5 - not a lot has changed in the world of relations!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.7.2
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.14
        "twig/extensions": "^1.5" // v1.5.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "fzaninotto/faker": "^1.7", // v1.7.1
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.4.0
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice