Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Data Transformer

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We built a custom field type called UserSelectTextType and we're already using it for the author field. That's cool, except, thanks to getParent(), it's really just a TextType in disguise!

Internally, TextType basically has no data transformer: it takes whatever value is on the object and tries to print it as the value for the HTML input! For the author field, it means that it's trying to echo that property's value: an entire User object! Thanks to the __toString() method in that class, this prints the first name.

Let's remove that and see what happens. Refresh! Woohoo! A big ol' error:

Object of class User could not be converted to string

More importantly, even if we put this back, yes, the form would render. But when we submitted it, we would just get a different huge error: the form would try to take the submitted string and pass that to setAuthor().

To fix this, our field needs a data transformer: something that's capable of taking the User object and rendering its email field. And on submit, transforming that email string back into a User object.

Creating the Data Transformer

Here's how it works: in the Form/ directory, create a new DataTransformer/ directory, but, as usual, the location of the new class won't matter. Then add a new class: EmailToUserTransformer.

The only rule for a data transformer is that it needs to implement a DataTransformerInterface. I'll go to the Code -> Generate menu, or Command+N on a Mac, select "Implement Methods" and choose the two from that interface.

I love data transformers! Let's add some debug code in each method so we can see when they're called and what this value looks like. So dd('transform', $value) and dd('reverse transform', $value).

... lines 1 - 7
class EmailToUserTransformer implements DataTransformerInterface
{
public function transform($value)
{
dd('transform', $value);
}
public function reverseTransform($value)
{
dd('reverse transform', $value);
}
}

To make UserSelectTextType use this, head back to that class, go to the Code -> Generate menu again, or Command + N on a Mac, and override one more method: buildForm().

Hey! We know this method! This is is the method that we override in our normal form type classes: it's where we add the fields! It turns out that there are a few other things that you can do with this $builder object: one of them is $builder->addModelTransformer(). Pass this a new EmailToUserTransformer().

... lines 1 - 9
class UserSelectTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new EmailToUserTransformer());
}
... lines 16 - 20
}

The transform() Method

Let's try it! I'll hit enter on the URL in my browser to re-render the form with a GET request. And... boom! We hit the transform() method! And the value is our User object.

This is awesome! That's the whole point of transform()! This method is called. when the form is rendering: it takes the raw data for a field - in our case the User object that lives on the author property - and our job is to transform that into a representation that can be used for the form field. In other words, the email string.

First, if null is the value, just return an empty string. Next, let's add a sanity check: if (!$value instanceof User), then we, the developer, are trying to do something crazy. Throw a new LogicException() that says:

The UserSelectTextType can only be used with User objects.

Finally, at the bottom, so nice, return $value - which we now know is a User object ->getEmail().

... lines 1 - 8
class EmailToUserTransformer implements DataTransformerInterface
{
public function transform($value)
{
if (null === $value) {
return '';
}
if (!$value instanceof User) {
throw new \LogicException('The UserSelectTextType can only be used with User objects');
}
return $value->getEmail();
}
... lines 23 - 27
}

Let's rock! Move over, refresh and.... hello email address!

The reverseTransform() Method

Now, let's submit this. Boom! This time, we hit reverseTransform() and its data is the literal string email address. Our job is to use that to query for a User object and return it. And to do that, this class needs our UserRepository.

Time for some dependency injection! Add a constructor with UserRepository $userRepository. I'll hit alt+enter and select "Initialize Fields" to create that property and set it.

... lines 1 - 9
class EmailToUserTransformer implements DataTransformerInterface
{
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
... lines 18 - 41
}

Normally... that's all we would need to do: we could instantly use that property below. But... this object is not instantiated by Symfony's container. So, we don't get our cool autowiring magic. Nope, in this case, we are creating this object ourselves! And so, we are responsible for passing it whatever it needs.

It's no big deal, but, we do have some more work. In the field type class, add an identical __construct() method with the same UserRepository argument. Hit Alt+Enter again to initialize that field. The form type classes are services, so autowiring will work here.

... lines 1 - 10
class UserSelectTextType extends AbstractType
{
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
... lines 19 - 28
}

Thanks to that, in buildForm() pass $this->userRepository manually into EmailToUserTransformer.

... lines 1 - 19
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new EmailToUserTransformer($this->userRepository));
}
... lines 24 - 30

Back in reverseTransform(), let's get to work: $user = $this->userRepository and use the findOneBy() method to query for email set to $value. If there is not a user with that email, throw a new TransformationFailedException(). This is important - and its use statement was even pre-added when we implemented the interface. Inside, say:

No user found with email %s

and pass the value. At the bottom, return $user.

... lines 1 - 9
class EmailToUserTransformer implements DataTransformerInterface
{
... lines 12 - 31
public function reverseTransform($value)
{
$user = $this->userRepository->findOneBy(['email' => $value]);
if (!$user) {
throw new TransformationFailedException(sprintf('No user found with email "%s"', $value));
}
return $user;
}
}

The TransformationFailedException is special: when this is thrown, it's a signal that there is a validation error.

Check it out: find your browser and refresh to resubmit that form. Cool - it looks like it worked. Try a different email: spacebar3@example.com and submit! Nice! If I click enter on the address to get a fresh load... yep! It definitely saved!

But now, try an email that does not exist, like spacebar300@example.com. Submit and... validation error! That comes from our data transformer. This TransformationFailedException causes a validation error. Not the type of validation errors that we get from our annotations - like @Assert\Email() or @NotBlank(). Nope: this is what I referred to early as "sanity" validation: validation that is built right into the form field itself.

We saw this in action back when we were using the EntityType for the author field: if we hacked the HTML and changed the value attribute of an option to a non-existent id, we got a sanity validation error message.

Next: let's see how we can customize this error and learn to do a few other fancy things to make our custom field more flexible.

Leave a comment!

59
Login or Register to join the conversation
Marcin Avatar

Hi Ryan!

Why didn't you inject EmailToUserTransformer to UserSelectTextType and call $builder->addModelTransformer($this->emailToUserTransformer)?

2 Reply

Hey Matt!

Wow, awesome question! Seriously - I wondered if someone would ask this - but so soon! I’m impressed :).

These data transformers are strange objects in a sense: they’re like services, they do work and don’t hold much data, but with one practical difference: we need to be able to configure them dynamically based on the options based to the form. As you’ll see in the next chapter(s), we add a field option to control the query. The only way for the form to pass that option to the transformer is through the constructor. That’s why we instantiate it manually instead of allow the container to do it.

To say it differently: when I coded up this tutorial, I DID first inject it via a type-hint. But once I needed to pass an option, I realized that wouldn’t work. It’s an odd situation, which causes this.

Cheers!

1 Reply
Default user avatar
Default user avatar Thomas Talbot | weaverryan | posted 4 years ago

Hello!

So, we use the constructor for passing options : each builded form has its own version of the Transformer.
But, how to "test double" the Transformer ? :(
Maybe use Prototype or service Factory ? 🤔

Reply

Hey Thomas Talbot!

Very fair question :). I would recommend 2 things:

1) For test purposes, allow the Transformer to be injected into your form class via a setTransformer() method. That's a little weird - just because we're creating this method ONLY for test purposes, but it would work. There are probably a few other ways to do this, and they're all probably fine.

2) Don't test your form class. This is actually what I would do. If you have heavy logic in your form class that you want to unit test, I would isolate that into its own class and test that instead. Also, eventually this form class WILL have a decent amount of logic - near the end of this tutorial we add some event listeners, etc. But, these *still* aren't great to unit test - they are small pieces that go into the overall picture of getting the form to function correctly. So, I would (A) isolate any logic that you *can* but ultimately (B) functionally test the form if you want to verify it's working.

Cheers!

Reply
Default user avatar
Default user avatar a name | posted 2 years ago | edited

Wouldn't UserToEmailTransformer be a better name than EmailToUserTransformer, based on which method is transform and which one is reverseTransform?

1 Reply

Hey there,

Yeah, I think you're right. the transform method expects a User and returns an email string. So, yes, the name of the transformer is inverted. I can tell you're paying attention to details ;)

Cheers!

1 Reply
Joel Avatar

Hello, I'm using the exact same approach in another context. When I'm dd()-ing the form, I'm getting the equivalent of an user object. Everything is null except the email. Is it normal?

1 Reply

Hey @Big Bob,

Sometimes yes, it depends on your code, you said it's another context, so if you don't have any issues with code it should be good.

Cheers!

Reply
akincer Avatar
akincer Avatar akincer | posted 4 years ago

The validation error message on the email not found doesn't match the exception message we created in reverseTransform in your video and on my end. Is this expected?

1 Reply
akincer Avatar

Sigh. Yet another instance of my question being answered in the next video. Next time I'm really going to wait till I watch the next video.

1 Reply
Lijana Z. Avatar

The last sentence in the video:

Next: let's see how we can customize this error and learn to do a few other fancy things to make our custom field more flexible.

Or they edited a video and added this later and so you did not hear.

Reply

Hey Coder,

I just double-checked this in Git - it looks like it was from the scratch :p

Cheers!

Reply

Hey Aaron,

Glad you had got your question answered faster than our team got to it. Yeah, we try to make screencasts short and finished, but sometimes we have to split a big topic into a few screencasts.

Cheers!

Reply

hello please can you take some time, to explain to me why you use dependency injection for the userrepository ? when you can instead call it as param in the function reverseTransform(UserRepository $userrepository),
thank you !!

Reply

Hey Soufiyane,

We need to inject the UserRepository first because the DataTransforme implements an interface and we can't just add more arguments to its methods, and second because we're not in charge of calling transform() or reverseTransform().

I hope this clarifies your doubts. Cheers!

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

I am not getting the user object at 3:32. I am getting the value of the database table column.
Also at 4:48 I get the error that it can only be used with the User objects. I am coding along on my own project, so I don't really know what I am missing.

On Article.php I have:
`
public function setConnectedSelectType(?User $connected_select_type): self

{
    $this->connected_select_type = $connected_select_type;

    return $this;
}

`

Ony ArticleController.php I have:
<br />$controlPlan->setConnectedSelectType($form["connected_select_type"]->getData());<br />

I don't have a UserController, but I see one in the 'finish' dir project of this course. Is that needed already?

Reply

Hey Farry,

I'd need to check your ArticleController, I believe you're not passing the User object when creating the form

Cheers!

Reply
Ad F. Avatar
Ad F. Avatar Ad F. | posted 2 years ago | edited

Can you give a good example where one could render a form with a list of products and each product should have a quantity field. I tried with embedded collection, but I just can't make it work. many thanks in advance there

Reply

Hey @cybernet2u

Sorry that I'm not Ryan but I have a good example for you ;) I think best way will be to not use symfony forms for such example and do something with Ajax. For example render inputs with current quantities as value and attribute with product ID. After you can do a js listener on input change post new quantity and persist it in database. Of course don't forget about security =)

Cheers!

1 Reply
Ad F. Avatar

since I lack ajax knowledge ... do you have an example for that :D ? ta

Reply

Hey @cybernet2u

That's a good question =) Of course I'd like to recommend you our JavaScript tutorials. Also we have a great chapter in our Upload tutorial where you can find a great example of ajax renaming object references: https://symfonycasts.com/sc...

Cheers!

Reply
Cyril Avatar

Hi !
I have a strange issue with TransformationFailedException(). I'm using a data transformer to convert an Author entity (to its firstname) into an input text of an Article form. But, as the relation is ManyToMany, I have a collection of Author in my form and when an author doesn't exist the reverseTransform method calls TransformationFailedException() as you did in the course. But I get this error :

InvalidPropertyPathException
Could not parse property path "children[authors].children[[0]]". Unexpected token "]" at position 30.

Any idea?
Your help would be appreciated!

Reply

Hey Cyril S.!

Hmm. That is *super* weird! On a high level, what you're doing makes sense. And also, this error is *so* weird that it "smells" like a possible bug somewhere in Symfony (or, at the very least, Symfony is not giving us a clear error). I'm honestly not sure what to do here :/. If you're able to post a project that reproduces the issue to GitHub, I'd be happy to take a look at it - it's one of those deep problems where I would need to be able to play with the code directly :).

Cheers!

Reply
Cyril Avatar

Hey Ryan.

I think I saw the problem but didn't resolve for now. When using a CollectionType in my form, each custom form field defined as entry_type is wrapped into an array. So, when flushing, doctrine says "I want an object and you give me an array with an object inside". That's the reason for theese double [[ ]].

If you want to take a look, I put a small project on GitHub : https://github.com/digitima...

You can download it, create sqlite database and run fixtures to test. I made two types of edit form : checkboxes || input text with data transformer. So you can compare data send to doctrine in each case.

Thank you very much for your help
Cyril, from France ;-)

Reply

Hey Cyril S. !

Ok, you get 100 points for a WONDERFUL reproducer project: it was clear and easy to set up! And now I know the problem!

1) In your data transformer, there was one minor mistake. This is actually not related to your problem, and my guess is that you probably made this mistake only on the test project. Anyways, in reverseTransform(), the first line should be findOneBy not findBy - it was causing authors to be an array of arrays instead of an array of Author objects.

2) Now, to your real problem. It is... a bug in Symfony. And it's already been fixed, but the fix hasn't been released. Here is the issue: https://github.com/symfony/symfony/issues/37027 and the pull request that fixes it https://github.com/symfony/symfony/pull/37085 - I confirmed that the changes in that pull request DO fix the problem. So, you'll need to wait for the next release (probably a couple of weeks, but maybe sooner) or use 5.0.8, which is the latest version of Symfony that does not have the issue.

Cheers!

Reply
Cyril Avatar

You're right with the first problem, it's a mistake I only made in the test project :-)
And for the real one, I will wait the next release! Thank you very must for the time spent on my case (and for your great tutorials, of course)

1 Reply

Hello,

as usual your explanation is really clear and very useful. I am disturbing you because I have a more complex problem to solve.

in my form I have a field "company" and a field "branch": when you choose a Company you get the list with the Branches of the company to choose from. I use jQuery to populate the branch form element. Originally I had two EntityType form elements and everything worked perfect (thanks Ryan).

However, my application has hundreds of companies to choose from, so I decided to put an autocomplete on this field. The autocompletion works perfectly and the DataTransformer on the company field is OK, but the Branch form element "forgets" it association with the Branch entity and I get a TransformationFailedException on the branch form element.

I tried to put a DataTransformer on the branch form element, but very strangely it always intercepts the "transform" action and never gets to "reverse transform".

What am I doing wrong?

Thank you for your attention, have a nice day!

Reply

Hey chieroz!

Hmm. So, yes, the data transformer should be attached directly to the "branch" field as you expected. Ignoring ALL other details, if you have a data transformer attached to a field, then when you submit, that transformer's reverseTransform should be called every time. It sounds like your problem is here: the reverse transform is NOT being called (but we don't know why). You also mentioned that the branch form element "forgets" its association with the Branch entity. What do you mean by that? Are you also using "form events" to set all of this up?

Let me know - and pose some relevant code if you can. I think there is something minor going on - maybe with how you're registering the field (especially if you're using form events, which can be complex).

Cheers!

Reply
Stephan Avatar
Stephan Avatar Stephan | posted 3 years ago | edited

Hi,

I developed the EmailToUserTransformer with the transform function but when I refresh the page, I have the message:
'The UserSelectTextType can only be used with User objects'
How can I solve that please?

Reply
Stephan Avatar

I solved it! I just forgot the use statement for User in EmailToUserTransformer class.

Reply

Hey Stephansav,

Ah, an easy fix! Glad you got it working!

Cheers!

Reply
Raymond L. Avatar
Raymond L. Avatar Raymond L. | posted 3 years ago | edited

Hello, When the custom submit the form with iframe from youtube, how can I select only the URL to store it in my data base and use in the template to customize like I want all the parameters of iframe like that <iframe src="{{ video.videoIframe }}" frameborder="0" height="109" width="105"></iframe>

Thanks for your help.

Reply

Hey Raymond L.!

Sorry - I'm not sure I understand. If your iframe is inside a form, the data from an iframe is not submitted to your server - so that data won't be available. You could use JavaScript to parse the iframe details and put this in a form (or send those via AJAX), but I'm not sure that I'm answering your question correctly :).

Cheers!

Reply
Raymond L. Avatar
Raymond L. Avatar Raymond L. | posted 3 years ago

Hello and thanks for all.

Reply
Igor K. Avatar
Igor K. Avatar Igor K. | posted 3 years ago

Hi guys!

I have a problem with an author field validation error.
In some previous chapter where we set annotations for email - like @Assert\Email() or @NotBlank(message="Please enter an email!"), it did not work. I think: pff fix that later. But when it did not work now... Hmmm, it kind annoying. So I had a question what could it be?
Don't work only for author validation error, the title works fine.
( I have --- Error: This value is not valid. HTML5 one)

Reply
Igor K. Avatar

Problem with author solved in next chapter, and with email i forgot about novalidate. Sorry to waste your time

Reply

Haha, no problem Igor, you are welcome :)

Reply
Andrea daniel C. Avatar
Andrea daniel C. Avatar Andrea daniel C. | posted 4 years ago

Hi Ryan,

my I ask you for your assistants.

I have a TextType (articleBundle) which works with jQuery autocomplete. When I select a value suggested by autocomplete, I render the "articleBundle" name in the field. Unfortunately the bundle Name is not unique, so that I can't use the data transformer on the articleBundle name property!

I have the articleBundleId which is unique and by clicking on one of the suggestions I put the Id in the jQuery object.

How do I take this id and pass it to the data transformer in order to get the right entity?

Thank you in advance and best regards!

Reply

Hey Andrea daniel C.

I think you can add a hidden field to your form so you can pass the ID, and then use it on your DataTransformer to select the right record. Give it a try and let us know how it went :)

Cheers!

Reply
Andrea daniel C. Avatar
Andrea daniel C. Avatar Andrea daniel C. | MolloKhan | posted 4 years ago | edited

Hi MolloKhan ,

first of all thank you very much for your assistance.

This is a very good idea my I ask you some questions about it.

When selecting one of the suggestions in the TextType field (articleBundle), I took the bundleId which is unique and stored it via js in the hidden field (bundleId).

On submit I took the Id from the hidden field, pass it to the data transformer and actualy I am getting the right record - WUHUU! :-)
BUT - I had to put the "mapped" => false flag to the articleBundle field otherwise I got the following error

Expected argument of type "App\Entity\ArticleBundle or null", "string" given at property path "articleBundle"

how do I take the record, which is now related to the hidden field (bundleId) and store it in the db - how do I tell Symfony4 to use the transformed object, related to the bundleId field and handle it as if it were inside the articleBundle field
Same question reverse - When editing a record, how do I take the reverse transformed object and, which is related to the hidden field (bundleId) and render it in the TextType field (articleBundle)?

Thank you in advance.

Best regards, Andrea

Reply

Hey Andrea daniel C.

Oh, yes, you have to un-mapped that field from your FormType because it's not related to any field. I would have to look at your code to get a better glance of what you are doing but what I think you can do is to manually grab the value from the form and then use it as you need to


// controllers method
...
$articleBundle = $form['fieldName']->getData();
// do more stuff here
...

Cheers!

Reply

Hi, I have followed all your great tutorials and think they are the most solid ones!

I am applying this tutorial to a DataTransformer for my own custom ChoiceType that is using a select2 component for storing article tags.

The transform method works, though i am basically using it to remove all data from the field because the select2 is being populated by ajax because of 'reasons'.

Now, with the reverseTransfrom method is a different story. Seems like the method is not even being accessed. Even the dd($value); at the beginning of the method is being missed. I googled around and saw that adding 'compound' => false to the options could help, but no luck for me. Has this something to do with the ChoiceType parent?

Reply

How are you handling that form? I believe the field name for your ChoiceType is incorrect and hence, Symfony thinks the field is empty.

P.S. Double check your post request

Cheers!

Reply

Thank you Diego! My field name is correct, it is called "tags", which is a many to many relationship.

FORM:
`
->add('tags', TagSelectType::class, [

            'compound' => false, //Already tried true, false or removing it
            'attr' => [
                'class' => 'select2'
            ]
        ])

`

TagSelectType:
`
public function buildForm(FormBuilderInterface $builder, array $options)

{
    $builder->addModelTransformer(new NameToTagTransformer($this->tagRepository));
}

public function getParent(){
    return ChoiceType::class;
}

`

And finally the NameToTagTransformer:
`
public function transform($value)

{
    dd($value); // <-----WORKS
}

public function reverseTransform($value)
{
    dd($value); // < does NOT WORK or even dies
}

`

The DataTransformer is being called correctly when loading the form (the transfrom method) but not when going back to the controller.

When dumping the whole $request on the controller, I am getting exactly what the select2 sends:
<br />array:3 [▼<br /> 0 => 8<br /> 1 => "Tag A"<br /> 2 => "Tag B"<br />]<br />

And this is why i need the datatransformer, as I am already receiving the existing tag ids (8 in the example), I want to save tag A and tag B, and then send the controller a proper array with only ids, so Symfony can set the manytomany relationship.

Reply

Ok, how are you doing the post request? Double check the field names that you are posting but if you are using the Form component for rendering the form, then you should not have this problem.
When you POST, does the other fields update?

Reply

Yes, I am using the form component for rendering the form. If I take that field out, everything saves perfectly, even with some related entities. But for this field, I get an error, because of course it is expecting an array of tag ids like ["1", "2", "3"], and is receiving ["1", "2", "New Tag A", "New Tag B"], so it does not know how to convert those strings to Tag entities. The strangest thing is that the dd() on reverseTransform is not even being fired. Does it have to do with many to many relationships? This is my tags field:

`/**

 * @ORM\ManyToMany(targetEntity="App\Entity\Tag", inversedBy="events")
 */
private $tags;`

Oh, and I don't know if it helps or has anything to do with it, but I am using symfony 4.2.4

Thanks a lot for your help!

PS: i have moved on to adding and removing tags full front-end with api endpoints on the tag controller, but I really want to do this the way the tutorial says.

Reply

Wait a second, before trying with a "view transformer", try changing your "TagSelectType" to be a TextType field instead of a ChoiceType

Reply

Hmm, the "dd()" it not being executed, so it's failing before calling "reverseTransform()"? It makes me think what you need is a "ViewTransformer". I'm not totally sure but give it a try: https://symfony.com/doc/current/form/data_transformers.html#about-model-and-view-transformers

You only have to change one line:


public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addViewTransformer(new NameToTagTransformer($this->tagRepository));
}
Reply

Thank you! I will give it a try ;)

Reply

Changing it to a TextType field instead of TagSelectType does send the data back and forth, but then I cannot use the transformer. However, changing the ChoiceType to a TextType in the parent() function of TagSelectType does trigger the "dd()"! Now I will just deal with transforming the request data coming as a string instead of an array and I'm done!

So I guess the issue has to do with the ChoiceType after all :)

Thanks a lot!!

Reply
Cat in space

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

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.8.0
        "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.2.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.6
        "symfony/console": "^4.0", // v4.1.6
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.6
        "symfony/framework-bundle": "^4.0", // v4.1.6
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.6
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.6
        "symfony/validator": "^4.0", // v4.1.6
        "symfony/web-server-bundle": "^4.0", // v4.1.6
        "symfony/yaml": "^4.0", // v4.1.6
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
        "symfony/dotenv": "^4.0", // v4.1.6
        "symfony/maker-bundle": "^1.0", // v1.8.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.6
    }
}
userVoice