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

Services

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.

It's time to talk about the most fundamental part of Symfony: services!

Honestly, Symfony is nothing more than a bunch of useful objects that work together. For example, there's a router object that matches routes and generates URLs. There's a Twig object that renders templates. And there's a Logger object that Symfony is already using internally to store things in a var/log/dev.log file.

Actually, everything in Symfony - I mean everything - is done by one of these useful objects. And these useful objects have a special name: services.

What's a Service?

But don't get too excited about that word - service. It's a special word for a really simple idea: a service is any object that does work, like generating URLs, sending emails or saving things to a database.

Symfony comes with a huge number of services, and I want you to think of services as your tools.

Like, if I gave you the logger service, or object, then you could use it to log messages. If I gave you a mailer service, you could send some emails! Tools!

The entire second half of Symfony is all about learning where to find these services and how to use them. Every time you learn about a new service, you get a new tool, and become just a little bit more dangerous!

Using the Logger Service

Let's check out the logging system. Find your terminal and run:

tail -f var/log/dev.log

I'll clear the screen. Now, refresh the page, and move back. Awesome! This proves that Symfony has some sort of logging system. And since everything is done by a service, there must be a logger object. So here's the question: how can we get the logger service so that we can log our own messages?

Here's the answer: inside the controller, on the method, add an additional argument. Give it a LoggerInterface type hint - hit tab to auto-complete that and call it whatever you want, how about $logger:

... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 10
class ArticleController extends AbstractController
{
... lines 13 - 41
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
... lines 44 - 48
}
}

Remember: when you autocomplete, PhpStorm adds the use statement to the top for you.

Now, we can use one of its methods: $logger->info('Article is being hearted'):

... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 41
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
// TODO - actually heart/unheart the article!
$logger->info('Article is being hearted!');
... lines 47 - 48
}
}

Before we talk about this, let's try it! Find your browser and click the heart. That hit the AJAX endpoint. Go back to the terminal. Yes! There it is at the bottom. Hit Ctrl+C to exit tail.

Service Autowiring

Ok cool! But... how the heck did that work? Here's the deal: before Symfony executes our controller, it looks at each argument. For simple arguments like $slug, it passes us the wildcard value from the router:

... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 38
/**
* @Route("/news/{slug}/heart", name="article_toggle_heart", methods={"POST"})
*/
public function toggleArticleHeart($slug, LoggerInterface $logger)
{
... lines 44 - 48
}
}

But for $logger, it looks at the type-hint and realizes that we want Symfony to pass us the logger object. Oh, and the order of the arguments does not matter.

This is a very powerful idea called autowiring: if you need a service object, you just need to know the correct type-hint to use! So... how the heck did I know to use LoggerInterface? Well, of course, if you look at the official Symfony docs about the logger, it'll tell you. But, there's a cooler way.

Go to your terminal and run:

./bin/console debug:autowiring

Boom! This is a full list of all of the type-hints that you can use to get a service. Notice that most of them say that they are an alias to something. Don't worry about that too much: like routes, each service has an internal name you can use to reference it. We'll learn more about that later. Oh, and whenever you install a new package, you'll get more and more services in this list. More tools!

Using Twig Directly

And check this out! If you want to get the Twig service, you can use either of these two type-hints.

And remember how I said that everything in Symfony is done by a service? Well, when we call $this->render() in a controller, that's just a shortcut to fetch the Twig service and call a method on it:

... lines 1 - 10
class ArticleController extends AbstractController
{
... lines 13 - 23
public function show($slug)
{
... lines 26 - 31
return $this->render('article/show.html.twig', [
... lines 33 - 35
]);
}
... lines 38 - 49
}

In fact, let's pretend that the $this->render() shortcut does not exist. How could we render a template? No problem: we just need the Twig service. Add a second argument with an Environment type-hint, because that's the class name we saw in debug:autowiring. Call the arg $twigEnvironment:

... lines 1 - 9
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 39
}
... lines 41 - 52
}

Next, change the return statement to be $html = $twigEnvironment->render():

... lines 1 - 9
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 32
$html = $twigEnvironment->render('article/show.html.twig', [
'title' => ucwords(str_replace('-', ' ', $slug)),
'slug' => $slug,
'comments' => $comments,
]);
... lines 38 - 39
}
... lines 41 - 52
}

The method we want to call on the Twig object is coincidentally the same as the controller shortcut.

Then at the bottom, return new Response() and pass $html:

... lines 1 - 8
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
class ArticleController extends AbstractController
{
... lines 14 - 24
public function show($slug, Environment $twigEnvironment)
{
... lines 27 - 32
$html = $twigEnvironment->render('article/show.html.twig', [
'title' => ucwords(str_replace('-', ' ', $slug)),
'slug' => $slug,
'comments' => $comments,
]);
return new Response($html);
}
... lines 41 - 52
}

Ok, this is way more work than before... and I would not do this in a real project. But, I wanted to prove a point: when you use the $this->render() shortcut method on the controller, all it really does is call render() on the Twig service and then wrap it inside a Response object for you.

Try it! Go back and refresh the page. It works exactly like before! Of course we will use shortcut methods, because they make our life way more awesome. I'll change my code back to look like it did before. But the point is this: everything is done by a service. If you learn to master services, you can do anything from anywhere in Symfony.

There's a lot more to say about the topic of services, and so many other parts of Symfony: configuration, Doctrine & the database, forms, Security and APIs, to just name a few. The Space Bar is far from being the galactic information source that we know it will be!

But, congrats! You just spent an hour getting an awesome foundation in Symfony. You will not regret your hard work: you're on your way to building great things and, as always, becoming a better and better developer.

Alright guys, seeya next time!

Leave a comment!

61
Login or Register to join the conversation
Default user avatar
Default user avatar Marine GREGOIRE | posted 4 years ago

First of all I want to tell you guys I love your tutorials, they're so well done and help A LOT !
I went through all of them but unfortunately haven't seen anything about dynamic subdomains in S4. Is there anything plan for these soon ?
Thanx a lot for all you've done ;)

2 Reply

Hey Marine GREGOIRE!

Thank you for your kind words - we're super happy any time they're useful ❤️❤️

unfortunately haven't seen anything about dynamic subdomains in S4

You're right! It's a bit of an edge-case feature. Well, I mean, it's not that rare - but we sometimes don't have time to get to more specific situations. However, a LONG time ago, we did have a small topic about this: https://symfonycasts.com/screencast/question-answer-day/symfony2-dynamic-subdomains

That is for Symfony 2, so you'll need to "translate" for Symfony 4. And, your project's requirements may make this vary a little... or a lot. But, the tl;dr is this (I'm assuming some things about your project):

1) Create an entity (e.g. Client, Site, etc) that has a subdomain property
2) Create a listener on the kernel.request event (or, the new event name RequestEvent), look at the current URL and find the correct Client based on that URL.
3) Set the "active" client on some SiteManager service, so that you can fetch that anywhere you need it and say something like $siteManager->getActiveClient().

Let me know if this helps - or if my assumptions about your app were way off ;).

Cheers!

1 Reply
Default user avatar
Default user avatar Marine GREGOIRE | weaverryan | posted 4 years ago

You're absolutely right. I'm going to go deeper in the one made in Symfony 2 then and follow your advice.
Because I'm in dev environment for now, I was wondering if I should use the Symfony binary to have a local webserver where I can test the subdomains? (Because I can't see how I'm supposed to do that with my localhost:8000 ;)

Reply

Hey Marine GREGOIRE!

GREAT question! The Symfony binary absolutely supports doing this - you can find the details here: https://symfony.com/doc/cur...

Cheers!

Reply
Default user avatar
Default user avatar Marine GREGOIRE | weaverryan | posted 4 years ago

Hi Ryan !
So I've managed to do all that and it works perfectly ! I even made my service available as a global for twig to get my client available on base.html.twig and everywhere on the website.
Thank you so much for your help and what you've done with the team with these casts. ;)

Reply
Default user avatar

Thanks Guys for this great tutorial. It was awesome and for me it is a new beginning . Peace !!!!!!!!

Best!

1 Reply

We're glad to hear it! Welcome to the Symfony world if you have questions about any tutorial just let us know :)

Cheers!

Reply

Awesome Dear Victor You know What I learn From You not Symfony only the biggest thing I learn from you Fun Feeeeeeeeeeeeeeeesssssssssssssssss!!!! When I was learning some coding then my mood was serious ed in that time when the error fall I frustrated and dejected from the code then I was thing is so hard. But In symfony every errror was Fun. You are of the one Instructor in the World That my Voice I would be proud of you. It's traaaaaaaaaaaaaaaap!

Thank's Sir I have no word to pay you thanks. I am obliged by you!!!

1 Reply

Haha, hey John99Dev! You have made our day! I've shared your message with our entire group (including Victor)! Now, keep going!! :D

Reply

Where you are you sharing my message I want to see Please link?

Reply

It's on internal private Slack channel :). We like to share happy users with each other!

Reply

Thank's Alooooooooooooooooooooooooooooooooooooooooooooooooooooot. and You made our Life Thank's

Reply

Haha, cheers John99Dev!

Reply
Hicham A. Avatar
Hicham A. Avatar Hicham A. | posted 3 years ago

I am definitely learning a lot, but I am still struggling a bit understanding some concepts. Is there any advice or if you guys have a roadmap on what I need to learn first before I tackle symfony? Thank you and great work

Reply

Hey Travel,

First of all, thank you for the nice feedback about SymfonyCasts!

Hm, it depends on what concepts exactly. Well, of course, Symfony is written in PHP, so I'd definitely recommend you to know more about PHP programming language. We have a track for this: https://symfonycasts.com/tr... . Also, Symfony is written using object-oriented programming (OOP) way and operates objects. Almost everything in Symfony a separate classes and its instances. So, to understand this OOP concept I'd recommend you to pass our OO track: https://symfonycasts.com/tr... . And after this you should have enough knowledge to start with Symfony. I'd recommend you to follow the latest Symfony 5 version, we have a separate track for this here: https://symfonycasts.com/tr... - we're working on releasing new tutorials right now.

I hope this helps! If you still have any questions - just let us know! And you may also ask more specific questions about the topic you didn't get in comments below the video.

Cheers!

Reply
Daniel M. Avatar
Daniel M. Avatar Daniel M. | posted 3 years ago

Hi,
When you pass $twigEnvironment as a function parameter in the minute 5:30, how does Symfony knows what to load from it? It isn't like the usual parameter you pass to a function, for example a string or an integer. I search a bit in Google and it seems related with Dependency Injection, but I don't understand how it works in this case. Maybe this is explained in a later video and I'm getting ahead, but I would appreciate if you can point me to any resource explaining this.

Thanks.

Reply

Hey @dan!

This is all powered by "autowiring", which works by the type-hint of the argument (in this case Environment). Earlier in this video, we run bin/console debug:autowiring - https://symfonycasts.com/screencast/symfony/services?playAt=250 - this gives us a list of all of the type-hints that we can use to "ask" the container for specific services. The Twig\Environment service is on that list.

So, you're 100% correct to ask "how does Symfony know what to load for the $twigEnvironment variable?". The answer is that the container (sometimes called the "dependency injection container") holds a lot of services (objects). When you want to "ask" for one of those services in a controller, you add an argument and use whatever type-hint gives you that service. When Symfony goes to call the controller, it sees the type-hint and asks the container to give it the correct service so it can pass it as the argument.

By the way, you will also see this autowiring thing used in the __construct() method of your own services - that's actually the main/most traditional place that you'll see it.

Let me know if that helps!

Cheers!

Reply
Daniel M. Avatar

Understood, thanks for the explanation :D

Reply
Hassan G. Avatar
Hassan G. Avatar Hassan G. | posted 4 years ago

Thank you very much for this great tutorial ❤️❤️ , I have a problem with the security-checker, someone has a track?

λ php bin\console security:check

In ErrorChunk.php line 105:

SSL peer certificate or SSH remote key was not OK for "https://security.symfony.co...".

In CurlResponse.php line 264:

SSL peer certificate or SSH remote key was not OK for "https://security.symfony.co...".

Reply

Hey Hassan G.

Is this problem still persist? It looks like temporary certificate issue, and it should work fine. Let us know if you still have this error.

Cheers.

-1 Reply
Hassan G. Avatar
Hassan G. Avatar Hassan G. | sadikoff | posted 4 years ago | edited

Hello @Vsadikoff, thank you for your return, I still have the error, I turned an composer update and the error persist

Reply

Hey Hassan G.!

Yes, our sensiolabs/security-checker package in the tutorial is out of date. If you want to fix it, you'll need to update the sensiolabs/security-checker entry in composer.json to be at least ^5.0 (or better, ^6.0), which is the newest :). Then run composer update sensiolabs/security-checker.

Let me know if this helps!

Cheers!

Reply
Krzysztof K. Avatar
Krzysztof K. Avatar Krzysztof K. | posted 4 years ago | edited

I have a question which is referring to Twig and Services.

Twig has a global app object which comes from AppVariable class.

What I would like to extend this class and use mine class for an app object.

How I can archive that?

I would like to add few more helper methods, in example in controller I want to access app object and add breadcrumbs:
$app->setBreadcrumbs($breadcrumbs)
and in template I will access it and render.

Reply

Hey Krzysztof K.

I'm glad to know that you could do it by yourself but I have one question for you. Instead of extending the global app object why just don't create a TwigExtension service where you can add your own functions and filters?

Cheers!

Reply
Krzysztof K. Avatar
Krzysztof K. Avatar Krzysztof K. | Krzysztof K. | posted 4 years ago | edited

ok, I have managed to do that myself.

`

namespace App\Service;

use Symfony\Bridge\Twig\AppVariable;
use Symfony\Component\DependencyInjection\ContainerInterface;

class App extends AppVariable
{

/**
 * @var ContainerInterface
 **/
protected $container = null;
protected $breadcrumbs = [];

public function __construct(ContainerInterface $container) {
    $this->container = $container;
}

/**
 * @return array
 */
public function getBreadcrumbs(): array
{
    return $this->breadcrumbs;
}

/**
 * @param array $breadcrumbs
 */
public function setBreadcrumbs(array $breadcrumbs): void
{
    $this->breadcrumbs = $breadcrumbs;
}

/**
 * @param array $breadcrumb
 */
public function addBreadcrumb(array $breadcrumb): void
{
    $this->breadcrumbs = array_merge($this->breadcrumbs, $breadcrumb);
}

}


    App\Service\App:
        arguments: ['@service_container']
        calls:
            - [setEnvironment, ["%kernel.environment%"]]
            - [setDebug, ["%kernel.debug%"]]
            - [setTokenStorage, ['@?security.token_storage']]
            - [setRequestStack, ['@?request_stack']]

twig:

globals:
    app: '@App\Service\App'

`

-1 Reply
Sebastian S. Avatar
Sebastian S. Avatar Sebastian S. | posted 4 years ago

Hello,
in the symfony 4.2.0 version there is probably a bug.
When you run:
./bin/console debug:autowiring
list of services is empty ...
See https://github.com/symfony/...

In my case i have to install 'reflection-docblock' from phpdocumentator
composer require phpdocumentor/reflection-docblock
This solved the problem

Reply

Hey Sebastian S.

You are completely right! There was a bug, but it's already fixed in version 4.2.1. Update you symfony version and you will see all services.

By the way thank you for your report and solution to bypass this bug.

Cheers!

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

Dear victor i have a problem with tail -f var/log/dev.log I get tail: cannot open ‘tail -f var/log/dev.log’ for reading: No such file of directory. Tail: no files remaining.
Please help me.

Reply

Hey Frank K.

Could you run ls var/log so we can see what files you have on that folder?

Side note: Double check that you are running on the "dev" environment, just open ".env" file and look for "APP_ENV" variable

Cheers!

Reply
Aleksandr T. Avatar
Aleksandr T. Avatar Aleksandr T. | posted 5 years ago

Hey, can you help me an advice how to resolve this issue:
I created a new class Tools in Service folder. In this class Tools I am trying to get the base path to the directory through
$basePath = $this->getParameter('kernel.project_dir');

But I have an error
"Call to a member function getParameter() on null"

I tried class Tools extends Controller but the error remained
https://yadi.sk/i/Ts6Mhe5m3...

If I run $basePath = $this->getParameter('kernel.project_dir') in ExampleController that in folder Controller it is NO error!!

Reply

Hey Aleksandr T.!

I can totally help! So, the getParameter() shortcut method is ONLY something that works when you're inside a controller. And now, you can't just extend Controller - that's a very clever idea, but it won't work. Basically, for convenience, there are a few things that controllers can do that other classes cannot do. And... that's no problem!

So, how can you get a parameter from inside a service? Well, normally, when you have a service (like your Tools class) that needs another service, we create a __construct method and use autowiring by type-hinting that argument. We start talking about that here, but we talk about it much more in the next tutorial - https://knpuniversity.com/screencast/symfony-fundamentals. However, you cannot autowire parameters, because they are not objects, so there is no type-hint to use. But, the process is almost the same: you should create a __construct() method with a $projectDir argument. Then, to tell Symfony what to pass for this, you'll use the bind keyword in services.yaml (https://knpuniversity.com/screencast/symfony-fundamentals/controller-constructor) - something like this:


services:
    _defaults:
        # ...
        bind:
            $projectDir: '%kernel.project_dir%'

Now, any constructor argument for any service that is called $projectDir will receive this value.

Cheers!

Reply
Aleksandr T. Avatar
Aleksandr T. Avatar Aleksandr T. | weaverryan | posted 4 years ago | edited

many thanks Ryan!
I trying but don`t understand how make right binding the service parameter $projectDir
I have an InvalidArgumentException
Unused binding "$projectDir" in service "App\Service\Tools".
I find you contribute
https://symfony.com/blog/new-in-symfony-4-1-autowiring-improvements
but my code still wrong

in config\services.yaml I add

services:
    _defaults:
        # ...
        bind:
            $projectDir: '%kernel.project_dir%'```

            

in my service class src\Service\Tools.php 

class Tools
{

private $projectDir;

public function __construct()
{
    $this->projectDir = $projectDir;
}

public function test()
{
    return $this->projectDir;
}

}`

but when i try to call

class Example extends Controller
{

    /**
     * @Route("/example", name="example")
     */
    public function test(Tools $tools)
    {
        dump($videoTools->test()); die;
    }
Reply

Hey Aleksandr T.

Ahh! You're SO close! You're just missing one detail. It's this in Tools


    public function __construct($projectDir)
    {
        $this->projectDir = $projectDir;
    }

You forgot to add the $projectDir argument to the __construct() method. The "bind" feature works by looking at the name of your constructor arguments and "binding" them to the value you specified. Because you didn't have this argument, the error was basically saying:

Hey! I see you configured a "bind" for $projectDir in your services.yaml file, but I don't see any $projectDir arguments in your app.

Let me know if this fixes it!

Cheers!

Reply
Aleksandr T. Avatar

Yeees, Ryan! It`s working now))) I am so...))
Thanks!

Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 5 years ago

Whats the difference between:

./bin/console debug:autowiring
and
./bin/console debug:container --types

I had the other command from a SF3 Tutorial of you. It looks more clean in the terminal.

Reply

Hey Mike P.!

GREAT question :). And sorry for my slow reply - I was away last week. Here's the PR where I added debug:autowiring: https://github.com/symfony/...

Basically, in Symfony 3.4, the autowiring types become much more important. And, the debug:container --types is a long name, and not easy to discover (you wouldn't easily be able to figure out that this command has a --types option). I also wanted something with a bit less information - hopefully something that looks MORE clean, not less clean ;).

But, this was also a "rushed" feature - I introduced this after the Symfony 3.4 feature freeze, and so it needed to be done quickly and not touch any other parts of the system. We're actively talking about how to not have these 2 separate commands anymore, and, instead, to make one command that really shows the BEST things possible. Here is some info: https://github.com/symfony/...

Overall, they how the same info, just displayed differently.

Cheers!

1 Reply
Mike P. Avatar

The new debug:autowiring over at Github looks so cool! Hopefully we'll see it soon! Thanks, you're awesome!

1 Reply
Sarah V. Avatar
Sarah V. Avatar Sarah V. | posted 5 years ago

Is it also considered good practice(or does it make any sense?) if you constructor inject 'EngineInterface' and declare 'templating engines' in the 'framework.yml' file?

Reply
Osagie Avatar

Hey Sarah,

Well, Symfony suggests using Twig as a template engine, and the question is: why don't you enough Twig that is secure and powerful? But if you need a few engines like Twig and PHP - I think that's OK to use both, it depends on your business logic.

Cheers!

Reply
Default user avatar
Default user avatar Junaid Farooq | posted 5 years ago

Is there going to be any addition to this course. This is my first ever introduction to Symfony framework and I have already started to like it.

Reply

Hey Junaid,

Yes! See Symfony 4 track for more courses: https://knpuniversity.com/t... - there're only . courses yet, there will be more soon.

You can also check Symfony 3 track: https://knpuniversity.com/t... - there much more topics covered, but you will need to do it in Symfony 4 way using Flex like we show here.

Cheers!

1 Reply
Default user avatar
Default user avatar Junaid Farooq | Victor | posted 5 years ago

Thanks Victor

1 Reply
Default user avatar
Default user avatar agentolivia | posted 5 years ago

I'm getting this error when I run `tail -f /var/log/dev.log`:

tail: /var/log/dev.log: No such file or directory

There is indeed no such file in /var/log. Is this a permissions problem maybe? Here's what I have:

/var/log
drwxr-xr-x
owner: root
group: wheel

I haven't changed any of the default configuration that was installed with monolog. Here is my config/packages/dev/monolog.yaml:

```yaml

monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
```

Reply

Hey agentolivia ,

Easy fix ;)

Just remove slash from the beginning of the path, i.e.

tail -f var/log/dev.log

When you start with "/" - you look for the file relative to the root of your file system, but we need to look for it relative to the Symfony project directory. And as you can see, we don't have leading slash in paths we have in this screencast.

Cheers!

Reply
Default user avatar
Default user avatar agentolivia | Victor | posted 5 years ago

Thanks for the extra set of eyes and the help!

Reply
Default user avatar
Default user avatar sandeep sangole | agentolivia | posted 5 years ago

Hi there , even I am not seeing dev.log inside log folder. How did you get it ?

27 Reply

Hey Sandeep,

This file is created when you load application in "dev" environment. But this path is different for Symfony 3 applications where logs are written in "app/logs/dev.log" or "var/logs/dev.log". So you need to figure out the path to logs for your *project*. But really, if you don't see logs file when load Symfony application and you even can't load the application at all, i.e. you see some server error (not Symfony error page) - then probably you need to check logs of your web server like Nginx / Apache.

Cheers!

Reply
Florian Avatar
Florian Avatar Florian | posted 5 years ago

Actually, i think it is good practice to inject the Environment service into your action and to not use the render shortcut.
This way you can mock the twig rendering for unit testing.

Reply

Hey Hansi,

If you want to unit-testing it - so yeah, it makes sense to inject Twig service. And that's totally fine I think. But mostly controllers are tested with functional tests, so it's ok to use shortcuts.

Cheers!

Reply
Default user avatar

Do all services get instantiated when symfony starts? Even services that you do not need. Thxs in advance.

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": "*",
        "sensio/framework-extra-bundle": "^5.1", // v5.1.3
        "symfony/asset": "^4.0", // v4.0.3
        "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/twig-bundle": "^4.0", // v4.0.3
        "symfony/web-server-bundle": "^4.0", // v4.0.3
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "sensiolabs/security-checker": "^5.0", // v5.0.3
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.3
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.3
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.3
    }
}
userVoice