If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe way we coded in Symfony 3 was a bit different than Symfony 4. And... well... we need to learn just a little bit about the Symfony 3 way. Why? Because, when you find bundles with outdated docs, or old StackOverflow answers, I want you to be able to translate that into Symfony 4.
In Symfony 3, services were defined as public. This means that you could use a $this->get()
shortcut method in your controller to fetch a service by its id. Or, if you had the container object itself - yep, that's totally possible - you could say $container->get()
to do the same thing.
But in Symfony 4, most services are private. What does that mean? Very simply, when a service is private, you cannot use the $this->get()
shortcut to fetch it.
At first, it might seem like we're just making life more difficult! But actually, Symfony 4 simply has a new philosophy.
Tip
You may not see the public: false
defined anymore because that’s the default value
starting in Symfony 4.
Open services.yaml
and, below _defaults
, check out the public: false
config:
... lines 1 - 5 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
... lines 9 - 10 | |
public: false # Allows optimizing the container by removing unused services; this also means | |
# fetching services directly from the container via $container->get() won't work. | |
# The best practice is to be explicit about your dependencies anyway. | |
... lines 14 - 34 |
Thanks to this, any service that we create is private. And so, we cannot fetch our services with $this->get()
. Increasingly, more and more third-party bundles are also making their services private.
And because so many services are now private, instead of using $this->get()
, we need to fetch services via "dependency injection" - a fancy, scary-sounding term that describes what we've been doing... this entire tutorial: passing services and config as arguments. This is considered a better coding practice than $this->get()
, which means that we get to write nice code. Woo!
And actually... it also makes your app faster! It's not huge, but private services are faster than public services.
Side note, if you do want to use the $this->get()
shortcut to fetch a public service - which you should not - you'll need to change your base controller class to Controller
instead of AbstractController
:
... lines 1 - 7 | |
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
... lines 9 - 12 | |
class ArticleController extends AbstractController | |
{ | |
... lines 15 - 83 | |
} |
It's not important why... and you shouldn't do it anyways :p.
Okay okay, so if we can't use the $this->get()
shortcut, how the heck can we fetch this service? Let's experiment! Copy the service id, find your terminal, and run:
php bin/console debug:container nexy_slack.client
Apparently the class for this object is Nexy\Slack\Client
. We didn't see any autowiring type-hints that would work in debug:autowiring
... but... maybe it will work if we type-hint this class?
Let's try it! In ArticleController::show()
, add another argument: Client
- make sure you get the one from Nexy\Slack
- then $slack
:
... lines 1 - 5 | |
use Nexy\Slack\Client; | |
... lines 7 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 36 | |
public function show($slug, MarkdownHelper $markdownHelper, Client $slack) | |
{ | |
... lines 39 - 79 | |
} | |
... lines 81 - 92 | |
} |
Add an if
statement: if $slug === 'khaaaaaan'
:
... lines 1 - 5 | |
use Nexy\Slack\Client; | |
... lines 7 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 36 | |
public function show($slug, MarkdownHelper $markdownHelper, Client $slack) | |
{ | |
if ($slug === 'khaaaaaan') { | |
... lines 40 - 44 | |
} | |
... lines 46 - 79 | |
} | |
... lines 81 - 92 | |
} |
Then we need to know about this! Go copy some code from the docs. Then, simplify a bit - we don't need the attachment stuff, this is coming from Khan
and the text should be "Ah, Kirk, my old friend.":
... lines 1 - 5 | |
use Nexy\Slack\Client; | |
... lines 7 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 36 | |
public function show($slug, MarkdownHelper $markdownHelper, Client $slack) | |
{ | |
if ($slug === 'khaaaaaan') { | |
$message = $slack->createMessage() | |
->from('Khan') | |
->withIcon(':ghost:') | |
->setText('Ah, Kirk, my old friend...'); | |
$slack->sendMessage($message); | |
} | |
... lines 46 - 79 | |
} | |
... lines 81 - 92 | |
} |
Excellent! Copy the slug. Then go to that page in your browser. And... it totally did not work: the $slack
argument is missing.
Well... I guess debug:autowiring
doesn't lie. Copy the $slack
argument to the constructor:
... lines 1 - 5 | |
use Nexy\Slack\Client; | |
... lines 7 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 20 | |
public function __construct(bool $isDebug, Client $slack) | |
{ | |
... line 23 | |
} | |
... lines 25 - 92 | |
} |
No, it won't work here either... but we will get a better error message. Actually, this is due to another shortcoming with controller autowiring: when it fails, the error isn't great. That will hopefully also be improved in the future. Again, a few of these features are still brand new!
Refresh! Ah, much better:
Cannot autowire service
ArticleController
: argument$slack
of method__construct()
references classNexy\Slack\Client
, but no such service exists.
This basically means that we're missing configuration to tell the container which services to pass for this type-hint. So... are we dead? No way! We are in total control of autowiring.
Open services.yaml
, then go copy the full class name for the client: Nexy\Slack\Client
. Back under bind
, instead of using the argument name, like $slack
, put the class name: Nexy\Slack\Client
. Set this to the target service id: @nexy_slack.client
:
... lines 1 - 5 | |
services: | |
# default configuration for services in *this* file | |
_defaults: | |
... lines 9 - 14 | |
# setup special, global autowiring rules | |
bind: | |
... lines 17 - 18 | |
Nexy\Slack\Client: '@nexy_slack.client' | |
... lines 20 - 35 |
That's it! Bind has two super-powers: you can bind by the argument name or you can bind by a class or interface. We're defining our own rules for autowiring!
Tip
You should comment this line out with nexylan/slack-bundle
>=2.2.0 to avoid
circular reference, as this alias is already added by the bundle
Let's make sure I'm not lying: refresh! Yes! There's our Slack notification.
But I want to make just one small tweak. In services.yaml
, instead of putting this beneath _defaults
and bind
, let's un-indent it so that it's at the root of services:
... lines 1 - 5 | |
services: | |
... lines 7 - 19 | |
# custom aliases for autowiring | |
Nexy\Slack\Client: '@nexy_slack.client' | |
... lines 22 - 37 |
Refresh again. It works exactly like before!
The difference is subtle. Config beneath _defaults
only affects services that are added in this file. But when you put this same config directly under services
, it will affect all services in the system. In practice... that makes no difference: only our code uses autowiring: third-party libraries do not using autowiring, to keep things predicatable.
The biggest reason I did this is that, when you run debug:autowiring
:
php bin/console debug:autowiring
and search for "Slack", there it is! When it's under bind
, it won't show up here.
But... this trick also shows us a bit more about how autowiring works. By putting this config directly under services
, we're creating a new service in the container with the id Nexy\Slack\Client
. But this is not a real service, it's just an "alias" - a "shortcut" - to fetch the existing nexy_slack.client
service.
Here's the important question: when an argument to a service hasn't been configured under bind
or arguments
, how does the autowiring figure out which service to pass? The answer is super simple: it just looks for a service whose id exactly matches the type-hint. Yep, now that there is a service whose id is Nexy\Slack\Client
, we can use that class as a type-hint. That's also why our classes - like MarkdownHelper
can be autowired: each class in src/
is auto-registered as a service and given an id
that matches the class name.
Ok, it's time to turn to something different, but very important in Symfony 4: environment variables. This will help us to not hardcode our secret Slack URL inside our code.
Hey B1!
Yes, you're 100% correct that starting in Symfony 4.0 (actually), all services default to public: false. For a while, we still shipped that public: false part there, even though it wasn't needed, but starting in Symfony 4.2, we removed it. Unfortunately, sometimes when a little tweak like this is made to the recipe, the docs aren't always updated perfectly - it's something we're working on actually :).
Thanks for pinging us on this - I think we may add a note so others aren't confused!
Cheers!
I'm guessing the real programmers know where to find this information (somewhere on Github), but not me. Where can I find that? It took me a while before I admitted defeat and found this post...
Hi Diego,
I was thinking about the Git-repo where the file lives and I could find some mentioning in git log. But, I'm just a hobbyist programmer, so just winging it...
Ah, I get it. This is the main Symfony repository: https://github.com/symfony/...
In there you can find all components and bundles (but not third party bundles)
nexy_slack has been updated and now supports autowiring, so when you un-indent it as at 6:18 you get a ServiceCircularReferenceException
Actually, it doesn't need the
Nexy\Slack\Client: '@nexy_slack.client'
config at all anymore
Hey there,
Yes, you're 100% right, that was an old problem and it's now fixed. Thanks for sharing it
Cheers!
Just a quick note for those tha t run into issues using the suggestion of installing 1.1.1 or 1.1.2 and so forth : there's a good chance the namespace is then "Maknz" and not Nexy. Quite a confusing intimidating lesson this one ;)
Hey Maxii123,
Thanks for this tip! Yep, it's easy to mess up namespaces, expecially if class name is the same. Keep eyes close to the namespaces you're autocompleting in PhpStorm :)
Cheers!
That's because Symfony4 uses Symfony Flex which leverage the "recipes" system. Ff a bundle has a recipe you will get a pretty default configuration automatically, plus, if the bundle is well designed, it will give you public services ready to use.
Is there any way to use third-party libraries with autowiring? Example: I download a library that sends messages to telegram and I want to receive it automatically in my controller, is this possible? when I say third-party libraries I'm referring to any library available in packagist
Hey RenatoAugustoFS
It depends on library if package/bundle provides public auto wireable service then you can just use it, but in most cases I think it won't be so, and you will need additional configuration to instantiate wireable services in your services.yaml, or create some internal factory that will use package class you need.
Cheers!
Hey there, I ran into some trouble I didn't see mentioned before here
Everything works fine until I add a use statement for Nexy\Slack\Client and autowire it. Then when I try to load any page of our app I get the following error:
No PSR-17 request factory found. Install a package from this list: https://packagist.org/providers/psr/http-factory-implementation<br />
I tried installing one of the packages from the list but that didn't help. I don't know what I'm doing wrong exactly
Yea... the newer versions of next/slack are much different to install... and confuse me. We added some notes to the video + script to guarantee that you install the older version to avoid all this. I talk about the newer version a bit here - https://symfonycasts.com/sc... - but (at least at this moment) the underlying way that this all works is almost more trouble than it's worth :).
Cheers!
At symfony 4.4.*. After making last edit for service.yaml, localhost:8000 gives me this error:
Circular reference detected for service "nexy_slack.client", path: "nexy_slack.client -> Nexy\Slack\Client -> nexy_slack.client".
Also I think it might do something with that [NOTE] I got before:
C:\the_spacebar>php bin/console debug:container nexy_slack.client
// This service is a public alias for the service Nexy\Slack\Client
Information for Service "Nexy\Slack\Client"
===========================================
---------------- -------------------
Option Value
---------------- -------------------
Service ID Nexy\Slack\Client
Class Nexy\Slack\Client
Tags -
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autoconfigured no
---------------- -------------------
! [NOTE] The "nexy_slack.client" service or alias has been removed or inlined when the container was compiled.
Hey Robertas Å .!
I know this error :). Basically, you no longer need to add the autowiring alias to services.yaml because a newer version of the bundle does this for you :). The reason you get this error is that the bundle originally did this incorrectly, which caused the problem. Newer versions of the bundle have it fixed. So basically, remove the autowiring alias from services.yaml. The lesson we're teaching is still valid (this is how autowiring works), but that specific thing is not needed anymore which is great!
Cheers!
In Symfony 4.4, I did not have to bind the service in services.yaml. It worked straight out-of-the-box for the slack message
Hey jpfortuno!
You're 100% right. That's because the bundle has since "fixed" the problem - they are creating the *exact* same alias that we are creating inside the bundle itself (so our alias isn't needed anymore).
Cheers!
I've had the cURL error 60 as well. I chose to go for the quick and dirty workaround of disabling the certificate verification. <i><b>Which is not recommended because it's not safe.</b></i>
But if anyone else just wants a really quick fix do this:
open the config\packages\httplug.yaml file and add factory and config/verify settings under app:
httplug:
clients:
app:
factory: 'httplug.factory.guzzle6'
config:
verify: false
<i>EDIT: Why the hell is there no indentation in this post?!?
I placed . for indentations now...</i>
Hey Karin W.!
Thanks for sharing the work-around in case you get the SSL issue when talking to the Slack API :). I also fixed your indentation - Disqus is *terrible* for writing source code - the button in the editor doesn't give you everything you need - lame!
Cheers!
Hi fellas,
I'm a bit confused on how I can autowire my aliases:
Let's say I have a service at App\Service\TestService. This is by default autowirable to a controller's method argument as it should. It is also appearing as expected in the debug:autowiring section.
Then I want to create an alias for that (just to understand autowiring a bit better). So I proceed to the services.yaml and I add the following:
-------------------------------------------
app.tester:
alias: App\Service\TestService
App\MyService\Test: '@app.tester'
-------------------------------------------
debug:autowiring now seems OK ->
App\MyService\Test alias for "App\Service\TestService"
But when I try to typehint an argument with "App\MyService\Test" I get the error: "The argument is type-hinted with the non-existent class or interface: 'App\MyService\Test'.
Anything obvious I might be missing?
Thanks
Hey christosp
Yeah, I got you, it's a confusing topic. Since your classes are being automatically registered, then you only have to set up an alias without registering the class again. E.G.
// services.yaml
services:
app.tester: '@App\MyService\Test' # Register the service "app.tester" as an alias of service "App\MyService\Test"
I hope it makes any sense to you :)
Cheers!
Hi MolloKhan and thanks for the reply.
Actually... I'm still a bit puzzled. My example is trying to make an alias to my existing class App\Service\TestService to an alias of a different class name App\MyService\Test (yeah, sorry about the slightly confusing - similar names) and then use the latter as a type hint.
Only reason that I tied to do this is by noticing that this exists already in some of the classes - for exampleKnp\Bundle\MarkdownBundle\MarkdownParserInterface alias for "markdown.parser.light"
and markdown.parser.light Knp\Bundle\MarkdownBundle\Parser\Preset\Light
So essentially, using the first one as type-hint results in the second one to be created.
Have I described it better?
Many thanks!
Nvm, I think I might have figured this out...
In my case, I was trying to create an alias to a non-existing class. debug:container
was not complaining about that and it was displaying the mapping ok, but when I was trying to type-hint the non existing class I was getting the error defined above (The argument is type-hinted with the non-existent class or interface: 'App\MyService\Test').
Then I realized that usually the bundles devs create aliases to the interfaces so they can use the Interfaces instead of the actual classes easier (that's the example above with the MarkdownParserInterface aliasing to markdown.parser.light and then finally resolving to Knp\Bundle\MarkdownBundle\Parser\Preset\Light). I guess it makes configuration and decoupling easier that way.
Only thing that I still might not get is the "circular" definition of some aliases/services. For example:
Symfony\Component\Filesystem\Filesystem alias for "filesystem"
and later filesystem Symfony\Component\Filesystem\Filesystem
Why it is displaying two times in the debug:container? I noticed it doesn't happen that way with other services, i.e.` assets.url_package
`
Once again, thanks a ton!
Hey christosp!
You are explaining things perfectly :). So I think you totally get it! Indeed, the way that 3rd party bundles do their aliasing is pretty cool - making the interfaces autowireable, and sometimes leveraging aliases to power their config system. For example, with KnpMarkdownBundle, based on your config, the MarkdownParserInterface alias might change from pointing to markdown.parser.light to some other service. That basically allows you to autowire MarkdownParserInterface everyone... and then change which "parser" you want via config... and everything will start using that.
About your last question: the first "alias for filesystem" is indeed saying that Symfony\...\Filesystem
is an alias to the filesystem
service. The second entry is actually saying that the filesystem
service is an instance of the Filesystem
class. It's basically say: "Hey! When you ask for the filesystem
service, I will do a new Filesystem()
to instantiate that. So that isn't an alias, but just the description of that service :).
Cheers!
I have service:
App\Service\Converter:
alias: converter
converter:
autowire: false
class: App\Service\Converter
Why, although the parameter for this service autowire: false, when I run
bin/console debug:autowiring
it return:
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
App\Service\Converter (converter)
I set parameter autowire for this service as "autowire: false".
Hey man!
If you disable the autowiring functionality on a service what it means is that that service's arguments won't be, well, autowired, you will have to specify all of its arguments. But, other classes that depends on such service will be able to be autowired with that service. Does it makes any sense to you?
Also, why you don't want it to be autowired?
Cheers!
Like me, the probably tried to turn off autowire so that the config steps outlined in this video for slack work with 4.+. Please see my Q regarding this ;)
Here I just added the use Nexy\Slack\Client and changed the $slack variable to our $client variable, and the message was sent perfecty! (on 4.3), also tested to add the Client in services.yaml, but this caused an circular reference error. At the end, my debug:autowiring use the class Nexy\Slack\Client directly, without alias, this is normal or the alias must be there anyway?
Thanks in advance,
Wagnner
Hey Wagnner L.
The SlackBundle just added support for the autowiring. Give it a check to this commit: https://github.com/nexylan/...
So, you don't need to set up the autowiring yourself as Ryan did on the video anymore :)
Cheers!
I had a similar "ciricular reference" problem when moving the Nexy/Slack/Client services bind to the top level so I thought I would try to "unwire" it and do it manually. No joy whatsoever. It's a bit of a problem with all the auto magic stiff. It seems so "obvious" when it works but soon becomes painful when it doesn't. So lets say I want to follow the vid by NOT have it autowired so the steps in the video work, I kind of thought this might work. Hopefully my intent is clear. Would you mind putting me right please ; )
top level in services.yaml:
Nexy\Slack\Client:
autowire: false
tags: ['nexy_slack.client']
App\Controller\ArticleController:
bind:
$slack: 'Nexy\Slack\Client'
error:
Too few arguments to function Nexy\Slack\Client::__construct(), 0 passed in /home/rgr/Dropbox/homefiles/development/Symfony/knp/02-Symfony4-Fundamentals/the_spacebar/var/cache/dev/ContainerAeZilLk/getArticleControllerService.php on line 15 and at least 1 expected
answering my own Q, this seemed to fixit: in services.yaml
` Nexy\Slack\Client:
autowire: false
arguments: ["%nexy_endpoint%"]
`
Does that seem reasonable? it really is a suck it and see thing when things are done outside of the script.
Hey Richard
Sorry for the super late reply (Somehow I lost track of this thread)
What you did looks OK but the autowire: false
is not needed. What you need to do is to define the arguments that cannot be autowired, for example any string arguments
I hope it helps. Cheers!
Hi there,
When I tried to acces the page http://localhost:8000/news/khaaaaaan I get the following error: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see http://curl.haxx.se/libcurl... , see screenshot: https://monosnap.com/file/f...
I've installed the following versions: "nexylan/slack-bundle": "^2.2", "php-http/guzzle6-adapter": "1.1.1".
I also tried to follow the tips from this page, but without success: https://stackoverflow.com/q...
How can I manage it?
Best regards,
Mike
Hey Bee!
Ah, bummer! Thanks for the screenshot - that's very helpful :). It's definitely a system setup issue related to SSL. So, unfortunately, the solution will be dependent on your operating system. What operating system are you using?
Cheers!
Hey Bee!
Ah, I was afraid of that! Just because fixing & installing things is often a bit more difficult on Windows :/. I'm not sure what the fix is... and it even depends on how you've installed PHP :/. If you're not using it already, you could try Mamp - I'm not sure if it would fix the issue, but certificate support is a pretty fundamental thing for a PHP installation to give you, so I would be surprised if quality php distributions like that don't offer it.
Sorry I can't help more!
Cheers!
I've tried on a Wamp server.
Also I'm using 7.3.5 PHP verison.
Anyway, thanks for your effort!
Cheers!
Hey Bee
I feel you, Windows is just so painful to work with but have you tried Windows 10 WSL (Windows Subsystem Linux)? It's quite nice if you are familiar with Linux
Cheers!
Hey
Thanks for your suggestion!
I have managed to solve this problem. I've added a wrong "cacert.pem" path, I changed it and now all seems to be alright!
Cheers!
I think my nexy_slack isn't being included properly. When you got that error installing the composer command it still worked but when i get it the last line of that error is: Installation failed, reverting ./composer.json to its original content.
So I feel like nexy_slack isn't getting pulled into the project(i don't get the auto complete for Client, it doesn't show the slack option.
EDIT: Long story short, it still worked eventually when I followed the rest I just had to manually put in the use statement at the top of the controller.
Hey Ryan S. ,
I'm glad you were able to solve this issue by yourself, and thanks for sharing your solution with others
Cheers!
I had a problem when updating the project. Composer uninstalled the nexy slack bundle and installed the nexy slack library. I had some message similar to
There is no extension able to load the configuration for "nexy_slack.
I had to remove the slack library manually and reinstall the slack bundle properly. If that could help anyone.
Hello, I have a little problem here. I created my own slack, added a webhook, added it to endpoint in nexy_slack.yaml, and after that added Nexy bind, imports, and params to show function, yet I run into "curl error 60 : The remote server's SSL certificate or SSH md5 fingerprint was deemed not OK." when i load the page
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.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
"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/twig-bundle": "^4.0", // v4.0.4
"symfony/web-server-bundle": "^4.0", // v4.0.4
"symfony/yaml": "^4.0" // v4.0.14
},
"require-dev": {
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
"symfony/dotenv": "^4.0", // v4.0.14
"symfony/maker-bundle": "^1.0", // v1.0.2
"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
}
}
So, regarding the public: false config in services.yaml, it's not there anymore in Symfony 4.2.2.
I guess it was removed probably because false is the default value for _defaults now, and not being there does not encourage the bad idea of changing it to true. I tried to look up something about this, and found the Service Container documentation for Symfony 4.2 to be outdated as it clearly shows public:false in the services.yaml example (in Automatic Service Loading in services.yaml section). ;)