17.
Bonus! LoggerTrait & Setter Injection
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.
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Whoops, an error! Please, try again later.
24 Comments
But why make a setter required, if you could just add it to the constructor and achieve the same result?
Is there a way to only inject dependancies that you actually used when calling service functions?
Hey boykodev
> But why make a setter required, if you could just add it to the constructor and achieve the same result?
Yep, you're correct but it was done that way so you end up with a reusable trait for logging messages and for teaching purposes.
> Is there a way to only inject dependancies that you actually used when calling service functions?
That's totally doable! it's called "Lazy Services", you can find more detailed info at the docs: https://symfony.com/doc/cur...
Cheers!
I'm not that "excited" about as setter injection are supposed to be optional and now you are making it required.
Any chance to see @required annotation accept somehow a condition using expression language or a fonction from the class itself?
Hey mickaelandrieu!
Yea, this is the key thing that I don't like about setter injection too. When I use it, I still code defensively - i.e. I check that the property IS populated before using it. And so, I only use it for true optional dependencies. Honestly, the logger is one of the few :). You could totally create a system full of traits like this so that you could easily "fetch" them via setter injection... but even for me, I think that's not a great world :).
Can you explain this a bit more? What kind of expressions would you use? The
@requiredis really more of a flag to Symfony's container that says: "Hey! I actually want you to call this setter and autowire it". The annotation was invented so that the container wouldn't try to call & autowire EVERY method starting with "set" :)Cheers!
Hello weaverryan!
I'm thinking about something like:
/**
* @required("kernel.debug == true") // need to think about what we could make available here...
*/
public function setLogger(LoggerInterface $logger = null) {}
Because the way I understand it, once you add the @required annotation the injection is not optional anymore!
So the only valid code (for me) would be:
/**
* @required()
*/
public function setLogger(LoggerInterface $logger) // will never be null in reality
{
// so no need to check for existence here!
}
Am I right, or there is some edge cases when - even with the @required annotation, the Logger could be null.?
Hey mickaël!
Sorry my slow reply on this - I got a little behind.
No, you’re totally right that once you have @required Symfony will call this 100% of the time. So, in practice, you don’t need to check for the existence of the property before you call a method on it.
It’s just that, from an object oriented standpoint, if you look st this class, nothing enforces that this settee was called. This could have practical implications in unit tests: if you forget to call setLogger, your code will fail. And actually, because the Logger really shouldn’t be needed to make the class work, you shouldn’t *need* to call setLogger(). So, by checking for existence, it just feels better from an OO perspective. And, I don’t need to unnecessarily call setLogger in a unit test.
Anyways, your idea about adding a little bit of logic in @required makes total sense! And that would totally be possible to implement. But, I don’t think it’s probably something that we need. Side note: if, for some reason, the Logger service were available in debug mode but not in prod mode, you could just make the argument optional (Logger $logger = null) as then it would not be set if the service didn’t exist (when the arg is required and the service doesn’t exist, you get an error).
Cheers!
That logger trait was awesome, I'm working on a Symfony 4.3 project so I'm a bit late but thanks for the valuable tips! Hoppefuly I can keep on getting jobs on Symfony with higher versions so I can learn with the latest tutorials versions of Symfony 6!
Hey Guilherme,
Thank you for your feedback! Well, maybe someday you will *upgrade* your project to Symfony 6? :) We do have a few tutorials about upgrading major version of Symfony, you need to upgrade to the latest 4.4 in your case, then drop all the deprecations you have and upgrade to the 5.x, then repeat the same steps but with a different Symfony version. It's totally doable, but it might depend on the bundles you're using, and how big your project is, but it's doable ;) I'd recommend you to take a look at this course: https://symfonycasts.com/sc... - it might help you to make a decision.
Cheers!
Yeah that's my plan for the future, I don't get to take archithecture decisions yet but I'm certainly very much interested in the topic and with SymfonyCasts I can acquire the necessary baggage to start participating the discussions and cave my road. W'ell get there in time hehe (don't mind my English haha)
Hey Guilherme,
Good plan! ;) And good luck with your project, I hope someday you will upgrade it
Cheers!
Last edit of ArticleController.php has incorrect code. Have a look. :)
Hey Robertas,
Thank you for this report! Unfortunately, it's difficult to spot the problem, could you point us, please? What exactly code is incorrect? DO you have a solution about how to fix it? I would be happy to fix the incorrect code for you :)
Cheers!
Sure, not so obvious, when the problem is not in the the code you don't have to change.
At line 37, there is old code:
But it should be:
Just minor issue.
Hey Robertas,
Thanks for more details about it! Though I still can't find the code block on current page where we show that invalid code. I only see this one: https://symfonycasts.com/sc... . The code you mentioned is from SlackClient, i.e. we moved it from the controller to the service: https://symfonycasts.com/sc...
Or are you talking about a different chapter page? Could you link with me with the exact code block? You can click on filename in code block to generate a link to the code block.
Cheers!
You can also do this to even simplify your trait:
`
/**
Trait LoggerAwareTrait
*/
trait LoggerAwareTrait
{
use \Psr\Log\LoggerAwareTrait;
/**
*
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
`
Hey Raito,
Thank for mentioning this "Psr\Log\LoggerAwareTrait". Moreover, you can just use this trait instead of creating your own one ;)
Cheers!
Trick to refactor
MarkdownHelperto useLoggerTraitbut log intomarkdownchannel:Step 1: refactor
MarkdownHelperas what you've done inSlackClientStep 2: Add following under
serviceskey ofservices.yamlto usemarkdownchannel forMarkdownHelper``App\Service\MarkdownHelper:
Hey Bohan Y.!
Nice tip! You're on fire :)
Cheers!
Thanks guys, seems that Symfony is inspired a lot on Java Spring :D
It was awesome that you repeated how to code an service (Service/SlackClient.php), it felt so great to do it on my own, without looking something up in the video/script and it worked instantly. Really motivating way of teaching Ryan!
One question, is it best practice to use:
return $this->slack->sendMessage($slackMessage);
instead of
$this->slack->sendMessage($slackMessage);
as last line inside the public sendMessage() function? (Video: 6:13 minutes left)
I'm running into a problem using this concept where I have a console command that uses trait X and service Y.
Service Y also uses trait X.
Trait X has a setXxx() method just like here.
The problem is that setXxx() is only called for the instatiation of the command, not for the service.
This results in all of the trait properties in the instantiated Y service being null.
Hey David P.!
Hmm. That is indeed strange - it should *not* work that way. Very simply, for each service (and both your console command AND service Y are services), the container looks to see if any of the public methods have the @required annotation above it. If it does, then it adds that as a "call" to the service. The *only* reason that it would not be doing this, is if service Y was not set to be autowired. But if you're using the default Symfony 4 config, all services that you register are autowired.
So, I'm at a loss, but I *am* interested: I just can't think of why it would *not* work. Would you mind posting some real (or at least realistic - you can change names) code? I might be able to spot the problem then :).
Cheers!
Oops. Failed to mention that the service, command, and trait are all part of a custom bundle (more about how I'm making that happen later).
I can post some code or turn this into a SO post. What would be best?
Thanks
Hey David!
Sorry for my slow reply! Ah, this help! The key thing is that the service needs to be autowired - the whole automatic setter injection thing happens thanks to autowiring. So, it makes me wonder if you might not be using autowiring in your custom bundle (actually, we usually don't use autowiring for shareable bundles, but if it's just your own code, it's totally fine). To find out, try running:
This has an "Autowired" line to tell you if your service is autowired :).
Cheers!
"Houston: no signs of life"
Start the conversation!