I would like to use a log library like e.g laminas-log or monolog within LaminasMvc and my question is how should I initiate it so that it is available in all models, controllers and views.
I have an idea how to do it if it would be only in controllers but not how to make them automatically available in models and views as well. (It would be great if this would be covered as well in one of the advanced tutorials as well.)
Provide a factory and alias it to the Psr\Log\LoggerInterface. Then simply inject it into the service that needs it.
and/or
You can also provide a delegator factory so you can quickly add it to a service without needing to change the current service factory. The Psr package ships with a LoggerAwareTrait and LoggerAwareInterface that can be used for that exact purpose. Your delegator factory would check that the service instance is an instance of LoggerAwareInterface and then would simply call $instance->setLogger($container->get(LoggerInterface::class));
This way, you are only incurring the factory overhead for service instances you are actually adding the logger to instead of all created instances if you were to use an initializer. Initializers check EVERY service that is created. Delegation only happens for service for which there is mapping in the config.
You could also wrap the logger in a view helper to provide access, or if you need it in an existing helper you can use the delegator on the ViewHelperPluginManager. In other words you would setup the delegation in the config for the View helper plugin manager, not under the āservice_managerā key if you are trying to target helpers for delegation.
Thatās what I would have suggested as well (although I was not aware of the Psr\Log\LoggerInterface, I should spend more time looking at Psrās specsā¦).
I think an example would be useful for people to understand the trait, interface and the use of delegators factories.
Unless there is a tutorial somewhere that we can link to (thatās giving me ideasā¦) or alternatively, a recipe in the Laminas Service Manager documentation.
@Tyrsson I just check the documentation and it seems that delegator factory is just available in Mezzio. So I need to do it as you explained in the first sentence.
If I may ask @modir what exactly are you trying to log? It may be simpler to just inject the logger into a listener and log exceptions etc from the provided events within the Mvc workflow.
@Tyrsson Sure you can ask. We have a software which communicates with APIs from UberEats and JustEat. Sometimes we have to make an API call to them sometimes they call a web hook on our side. Very often when a web hook is called we make changes on the data in the database. At the moment we have many error_log() messages in order to find out where something breaks. Because the problem could be on our side or e.g. on Uberās side. And for this part of the code I would like to have a better solution than error_log().
Now, this code is for mezzio but you just need the factories and maybe the processor. As you probably know the only different really is that the config for mezzio is provided by a ConfigProvider and in laminas its from a config file usually. But this is how I factoried Monolog for Mezzio. You can do the same in laminas mvc. Just check the getDependencies() method. Thats where you factories are registered. Ignore the middleware since they are not relevant.
(this is just development code but should get ya started)
Well, you might consider wrapping the logger in a listener aggregate. Provide an event to pass the error/exception and just tie into the event manager aware functionality that is already running in the Mvc. For a laminas db layer it already supports events so you would have access to the event manager. For other services just add the correct interface and trait and the em initializer will auto inject it for you. As long as itās not managed by a plug-in manager. You could then Just trigger you log event and set the channel which you could use to filter the messages.
Just thought I would mention it since that initializer is already running in the Mvc.
The short answer for this is pretty straight forward. Some of the Psrās and many of the laminas/mezzio packages use what I think of as a service āawareā pattern. It simply introduces an interface and a paired trait. The interface can be targeted via instanceof without needing to branch on a method_exist check. Which allows initializers or preferrably a delegator to target an āawareā service instance for injection simplifying decoration at the factory layer of the application.
Not as to say that you cant duck type an instance and perform a method_exist check, at which time you would only need to use the trait and not the interface. I would argue against this though since the interface is what should be used to create the contract which can be typed.
This is clearly more dirty than all other solutions suggested until then. But I thought Iād add a more pragmatic alternative.
If you are willing to make an exception for the log service to be global (), for the sake of ease of use from anywhere, then you can configure a factory as usual for the logger service, and then have a single global function similar to this:
function _log(): LoggerInterface
{
global $container;
return $container->get(LoggerInterface::class);
}
Of course, this comes with limitations. One of them is that itās probably a very bad idea in a Swoole / ReactPHP type of environnement. So make an informed decision, according to the tradeoff you are willing to make in your specific project.
Ewwww ā¦ that 's really dirrrty (as you said already). Sure ā¦ it works for the moment. But ā¦ dude! That 's strictly on of those things in PHP you shouldn 't do anymore.
For those who are actually thinking about implementing this approach: DON 'T DO IT!
Using dependency injection and a factory is the way to go. If you really need it automatically in everything everywhere ā¦ use an abstract factory. Laminas serves everything you need for it.