I'm migrating my app from Slim/3 to Slim/4. Perhaps I'm confused because there're endless syntaxes for the same stuff but I composed this:
use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
require dirname(__DIR__) . '/vendor/autoload.php';
class Config extends Container
{
}
class Foo
{
protected $config;
public function __construct(Config $config)
{
$this->config = $config;
}
public function __invoke(Request $request, Response $response, array $args): Response {
var_dump($this->config->get('pi'));
return $response;
}
}
$config = new Config();
$config->set('pi', M_PI);
var_dump($config->get('pi'));
AppFactory::setContainer($config);
$app = AppFactory::create();
$app->get('/', \Foo::class);
$app->run();
... and it isn't working as I expected because I get two entirely different instances of the container (as verified by setting a breakpoint in \DI\Container::__construct()):
The one I create myself with $config = new Config();.
One that gets created automatically at $app->run(); and is then passed as argument to \Foo::__construct().
What did I get wrong?
The container attempts to resolve (and create) a new instance of the \DI\Container class, since this is not the interface Slim uses. Instead, try declaring the PSR-11 ContainerInterface. Then the DIC should pass the correct container instance.
Example
use Psr\Http\Message\ServerRequestInterface;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
The same "rule" applies to the request handler interface.
Full example:
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class Foo
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function __invoke(
Request $request,
Response $response,
array $args = []
): Response {
var_dump($this->container);
}
}
Just a last note: Injecting the container is an anti-pattern. Please declare all class dependencies in your constructor explicitly instead.
Why is injecting the container (in the most cases) an anti-pattern?
In Slim 3 the "Service Locator" (anti-pattern) was the default "style" to inject the whole (Pimple) container and fetch the dependencies from it.
The Service Locator (anti-pattern) hides the real dependencies of your class.
The Service Locator (anti-pattern) also violates the Inversion of Control (IoC) principle of SOLID.
Q: How can I make it better?
A: Use composition and (explicit) constructor dependency injection.
Dependency injection is a programming practice of passing into an object it’s collaborators, rather the object itself creating them.
Since Slim 4 you can use modern DIC like PHP-DI and league/container with the awesome "autowire" feature. This means: Now you can declare all dependencies explicitly in your constructor and let the DIC inject these dependencies for you.
To be more clear: "Composition" has nothing to do with the "Autowire" feature of the DIC. You can use composition with pure classes and without a container or anything else. The autowire feature just uses the PHP Reflection classes to resolve and inject the dependencies automatically for you.
This happens as a result of how PHP-DI auto-registers itself. As of writing this answer, PHP-DI container auto-registers itself to the key DI\Container, and also the three implemented interfaces, on creation (see these lines of Container.php). As a result, if you type hint your constructor parameter against DI\Container or one of the three interfaces it implements, (which includes Psr\Container\ContainerInterface), PHP-DI is able to resolve itself.
ُThe problem is the use of self::class (line 110 of that file) makes DI\Container key somehow hard-coded, so although you're creating a child class of DI\Container (Config) the container still registers to same key as before. One way to overcome this is to let the container know that Config should also be resolved to itself. I see two options for this:
To register the container to same key as its class name, like what DI\Container does (This seems to be the right way to do it)
Manually registering the container after instantiating it
Here is a fully working example:
<?php
require '../vendor/autoload.php';
use DI\Container;
use Slim\Factory\AppFactory;
use Psr\Container\ContainerInterface;
use DI\Definition\Source\MutableDefinitionSource;
use DI\Proxy\ProxyFactory;
class Config extends Container
{
public function __construct(
MutableDefinitionSource $definitionSource = null,
ProxyFactory $proxyFactory = null,
ContainerInterface $wrapperContainer = null
) {
parent::__construct($definitionSource, $proxyFactory, $wrapperContainer);
// Register the container to a key with current class name
$this->set(static::class, $this);
}
}
class Foo
{
public function __construct(Config $config)
{
die($config->get('custom-key'));
}
}
$config = new Config();
$config->set('custom-key', 'Child container can resolve itself now');
// Another option is to not change Config constructor,
// but manually register the container in intself with new class name
//$config->set(Config::class, $config);
AppFactory::setContainer($config);
$app = AppFactory::create();
$app->get('/', \Foo::class);
$app->run();
Please note: As best practices suggest, you should not type hint against a concrete class (DI\Container or your Config class), instead you should consider type hinting against the interface (Psr\Container\ContainerInterface).
The problem is a misuse of a PHP-DI feature called autowiring:
Autowiring is an exotic word that represents something very simple:
the ability of the container to automatically create and inject
dependencies.
In order to achieve that, PHP-DI uses PHP's reflection to detect what
parameters a constructor needs.
If you use a factory method to create the container you can disable autowiring and the "strange" behaviour stops:
$builder = new ContainerBuilder(Config::class);
$builder->useAutowiring(false);
$config = $builder->build();
But I guess a better solution is to learn how to use autowiring properly :)
I had overlooked all these details because my code was originally written for Slim/3, which used Pimple as hard-coded default container. I had wrongly assumed they would work similarly but, albeit being container solutions, both libraries are quite different.
Related
I have a Slim Php (slim4) application to which I added Monolog for logging purposes. I'm adding the logger to the application like this:
$containerBuilder->addDefinitions([
LoggerInterface::class => function (ContainerInterface $c) {
$logger = new Logger('appname');
...
return $logger
This works fine for injecting the logger in most of my classes, by just doing:
public function __construct(ContainerInterface $container = null, LoggerInterface $logger)
{
// I can use $logger here
Now I'd also like to use the logger in the middleware like authentication. I don't see how I can properly do this.
I can get this working by adding the logger as a named entry in the container like this:
$containerBuilder->addDefinitions([
"LoggerInterface" => function (ContainerInterface $c) {
and then passing it to the middleware as a constructor parameter by getting it back from the container:
$middlewares[] = new MyAuthentication(..., $container->get('LoggerInterface'));
But this:
a) breaks the injection by classname for the other classes
b) is
apparently not a best practice
So what is the correct way to get this logger injected in the middleware?
Without adding the LoggerInterface as a named entry to the container, could you just inject the LoggerInterface class directly to your middleware via $container->get()? I.E. in a routes.php App function:
$container = $app->getContainer();
$app->add(new MiddleWare\MyAuthentication(..., $container->get(LoggerInterface::class)));
In short, I do not think you are going to be able to auto-wire the dependencies for the middleware as they need to be constructed before being appended to the router. You would need to explicitly inject the dependency as suggested by #Woodrow, although I would opt for method injection instead of constructor injection for the LoggerInterface as it would adhere to the LoggerAwareInterface.
You could let the dependency injection container (PHP-DI) resolve and inject all dependencies into your middleware:
LoggerInterface::class => function (ContainerInterface $container) {
return new Logger('name');
},
Then just register the middleware with the class name:
$app->add(\MiddleWare\MyAuthentication::class);
(You should not inject the container itself into the middleware)
That's all.
I'm trying to update Symfony 2.8 to Symfony 4 and I am having serious problems with the Services Injection.
I'm looking the new way to use Services inside Controllers, with auto-wiring:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
....
This way works fine, but I dislike the explicit way to refer MyService as a parameter of the function. This way I don't even need to register the Service in the services.yaml
Is there any way to use Services as in Symfony 2.8:
class DefaultController extends Controller
{
public function index()
{
$var = $this->get('AuxiliarService')->MyFunction(); /*Doesn't need to be explicit indicate before*/
....
With the services.yaml
services:
auxiliar_service:
class: AppBundle\Services\AuxiliarService
arguments:
entityManager: "#doctrine.orm.entity_manager"
container: "#service_container" #I need to call services inside the service
This way I don't need to indicate the Service as a parameter in the function of the Controller. In some cases, inside a Service, I need to call more than 10 services depends on the data, so indicate them as a parameter in the function is annoying.
Another doubt in Symfony 4, is how to call a Service inside another Service without pass it as an argument or parameter. It used to be possible by injecting the service container to be able to call a service inside a service:
$this->container->get('anotherService')
In Symfony 4, I think it is more expensive (in code) use Service because you have to explicitely indicate them when you are going to use them.
tldr; you can achieve that by using Service Subscribers & Locators.
In your controller:
use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
public function index(AuxiliarService $service)
{
$var = $service->MyFunction();
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// services you want to access through $this->get()
'auxiliar_service' => AuxiliarService:class,
]);
}
// rest of the implementation
}
If your service needs to implement a similar pattern, you'll need to implement ServiceSubscriberInterface (AbstractController, that you are extending for your controller, already does that for you).
class AuxiliaryService implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function has(string $id): bool
{
return $this->container->has($id);
}
protected function get(string $id)
{
return $this->container->get($id);
}
public static function getSubscribedServices()
{
return [
// array_merge is not necessary here, because we are not extending another class.
'logger' => LoggerInterface::class,
'service2' => AnotherService::class,
'service3' => AndMore::class
];
}
}
That being said, you are very probably not doing things right if you want to continue this way
Before Symfony 4+ you could do $this->get('service') because these controllers all had access to the container. Passing the dependency container around for this it is an anti-pattern, and shouldn't be done.
If you do not declare your dependencies, your dependencies are hidden. Users of the class do not know what it uses, and it's easier to break the system by changing the behaviour of one of the hidden dependencies.
Furthermore, with Symfony providing auto-wiring and a compiled container; dependency injection is both easier to implement and faster to execute.
That you are having trouble with implementing this probably reveals deeper issues with your code in general, and you should do some work on segregating the responsibilities of your classes. The fact that one service may depend on that many other services which you can't even know until runtime it's a very strong smell that the concerns are not well separated.
Try to adapt to the changes, it will do your application and yourself good in the long term (even if brings a small amount of pain right now).
I am using PHP-DI 6 Container in my PHP Project. At the very begging of my program I just initialize the container and get Application class with all dependencies injected.
$container = new Container();
$application = $container->get(Application::class);
$application->initialize();
$application->run();
On the image below you can see classes that I use in my project.
Asterisk Dispatcher gets injected into Application class.
private $asteriskDispatcher;
public function __construct(AsteriskDispatcher $asteriskDispatcher)
{
$this->asteriskDispatcher = $asteriskDispatcher;
}
Then, inside AsteriskDispatcher class I need to create a list of Asterisk Manager instances, which is going to contain some dependencies as well in near future.
I don't want to inherit container through the all classes. Is there a way to initialize PHP-DI container as singleton, so I can use it any moment I want to create some objects?
This is how I am doing this right now, I just create a new instance of PHP-DI container inside my AsteriskDispatcher class and this looks so damn awful.
class AsteriskDispatcher implements AsteriskDispatcherInterface
{
private $listOfAsteriskManagers;
public function __construct()
{
$configurations = AsteriskConnectionsConfiguration::$connectionsConfiguration;
$this->listOfAsteriskManagers = new \SplDoublyLinkedList();
$container = new Container();
foreach ($configurations as $configuration)
{
$this->listOfAsteriskManagers->push($container->make(AsteriskManager::class,
array('configuration' => $configuration)));
}
}
}
I really want to understand how can I use PHP-DI container without breaking SOLID principles.
From the documentation:
If you need to use the make() method inside a service, or a controller, or whatever, it is recommended that you type-hint against FactoryInterface *. That avoids coupling your code to the container. DI\FactoryInterface is automatically bound to DI\Container so you can inject it without any configuration.
*emphasis mine
So you should change your AsteriskDispatcher constructor to be like:
public function __construct(FactoryInterface $factory) {
// your code ...
// more of your code ...
$factory->make(AsteriskManager::class, ['configuration' => $configuration]);
// the rest of your code.
}
PS: Singletons are evil (mostly).
I have made a repository pattern app, having a repo and interface:
class UserRepository extends EloquentRepository implements UserRepositoryInterface
{
public function __construct()
{
$this->model = new User();
}
...
}
The repository and interfaces as well as extended class and its interface is registered in service provider and is called on app boot.
The questions I have are:
Is there a need to watch out for the order of registering? For example, should EloquentRepository class be loaded before the
repo, or does Laravel handle that on its own?
In case I inject UserRepositoryInterface in a controller, is the constructor method called automatically even though I didn't really new-up a class?
How long does the DI injection "live"? If I inject it in a page controller which calls some other controller and needs the same dependency, does the constructor call twice then, and operate separately in each controller?
Is there a difference if I call it like App::make() instead of DI?
Is there a need to watch out for the order of registering? For example, should EloquentRepository class be loaded before the repo, or does Laravel handle that on its own?
I don't quite understand where you would load EloquentRepository as (from the code posted) it seems you're only extending it. Which shouldn't be a problem.
In case I inject UserRepositoryInterface in a controller, is the constructor method called automatically even though I didn't really new-up a class?
Yes. Most of Laravel's main classes (controllers included) are loaded with DI in mind and the dependencies will be resolved automatically.
That being said, since you are injecting an interface and an interface by default cannot be initialized as a class, since it has no implementation - you need to bind an implementation to the interface first in order to use it.
How long does the DI injection "live"? If I inject it in a page controller which calls some other controller and needs the same dependency, does the constructor call twice then, and operate separately in each controller?
My understanding is that a new instance of the class will be created when the next controller is initialized. Unless you bind a class as a singleton.
Is there a difference if I call it like App::make() instead of DI?
App::make(some::class) will automatically resolve the dependencies of class some.
For example:
namespace App;
use App\Dependancy;
class Test
{
protected $d;
public function __construct(Dependancy $d)
{
$this->d = $d;
}
}
If you call this in the controller: $a = new \App\Test() you will get an error that \App\Test constructor expects class Dependency as first parameter.
But if you initialize it like this: $a = \App::make(\App\Test::class) the Dependency will be automatically resolved.
try to make the repositories abstract in the controllers and inject these through constructor.
like this here:
public function __construct(EloquentRepository $repository)
{
$this->repository = $repository;
}
And in the AppServiceProvider you can inject repositories you will need.
public function boot()
{
// provides any Repository in SomeController
$this->app->when(SomeController::class)
->needs(EloquentRepository::class)
->give(function (Application $app) {
return $app->make(SomeRepositoryInterface::class)
});
}
I'm trying to inject the service manager into a controller.
Actual Error:
\vendor\zendframework\zend-servicemanager\src\Exception\ServiceLocatorUsageException.php:34
Service "Project\Service\ProjectServiceInterface" has been requested to plugin manager of type "Zend\Mvc\Controller\ControllerManager", but couldn't be retrieved.
A previous exception of type "Zend\ServiceManager\Exception\ServiceNotFoundException" has been raised in the process.
By the way, a service with the name "Project\Service\ProjectServiceInterface" has been found in the parent service locator "Zend\ServiceManager\ServiceManager": did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code?
The process goes:
class BaseController extends AbstractActionController implements ServiceLocatorAwareInterface
{
public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
{
$this->serviceLocator = $sl;
}
}
Create controller and use constructor method
Extend this BaseController to AdminController
Setup Routes to AdminController => /admin
use Module.php
public function getControllerConfig()
Use closer as factory to create controller object injecting the serviceLocator
'Project\Controller\Project' => function($sm) {
$serviceLocator = $sm->getServiceLocator();
return new \Project\Controller\ProjectController($serviceLocator);
},
try to use $this->getServiceLocator()->get('service_name')
Exception found for missing service.....
Now the problem is this:
/**
*
* #param ServiceLocatorInterface $sl
*/
public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
{
$rtn = $sl->has('Project\Service\ProjectServiceInterface');
echo '<br />in Constructor: '.__FILE__;var_dump($rtn);
$this->serviceLocator = $sl;
}
public function getServiceLocator()
{
$rtn = $this->serviceLocator->has('Project\Service\ProjectServiceInterface');
echo '<br />in getServiceLocator: '.__FILE__;var_dump($rtn);
return $this->serviceLocator;
}
Within the __constructor() the service IS FOUND. Within the getServiceLocator() method the service with the same name IS NOT FOUND....
in Constructor: Project\Controller\BaseController.php
bool(true)
in getServiceLocator: Project\Controller\BaseController.php
bool(false)
Am I missing something? Is the SharedServiceManager doing something here?
The entire purpose of this exercise was due to this message:
Deprecated: ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. ...
If you really need the ServiceLocator, you have to inject it with a factory
Something like this
Controller:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorInterface;
class BaseController extends AbstractActionController
{
protected $serviceLocator = null;
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->setServiceLocator($serviceLocator);
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
Factory:
<?php
namespace Application\Controller\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Controller\BaseController;
class BaseControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator);
{
$controller = new BaseController($serviceLocator->getServicelocator());
return $controller;
}
}
?>
in module.config.php
<?php
// ...
'controllers' => [
'factories' => [
'Application\Controller\BaseController' => 'Application\Controller\Factory\BaseControllerFactory',
// ...
],
// ...
In Zend Framework 2 there are multiple service locators (docs here), one general (mainly used for your own services), one for controllers, one for view helpers, one for validators, ... The specific ones are also called plugin managers.
The error message you are receiving is just telling you that you are using the wrong service locator, the ones that retrieves controllers and not the general one. It is also suggesting you how to solve your problem:
did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code
What is probably happening (not 100% sure about this) is that in the constructor you are passing in an instance of the general service manager, and everything works fine with it. Then, since the controller implements the ServiceLocatorAwareInterface, the controller service locator is injected into your controller, overriding the one that you defided before.
Moreover, I think that the idea beyound the decision of removing ServiceLocatorAwareInterface in version 3 is that you don't inject the service locator inside your controller, but instead you inject directly the controller dependencies.
You should try to prevent injecting the service manager or service locator in the controller. It would be much better to inject the actual dependencies (in your case 'Project\Service\ProjectServiceInterface') directly into the __construct method of your class. Constructor injection (the dependencies are provided through a class constructor) is considered best practice in ZF2.
This pattern prevents the controller from ever being instantiated without your dependencies (it will throw an error).
If you inject a ServiceLocator or ServiceManager from which you will resolve the actual dependencies in the class, then it is not clear what the class actually needs. You can end up in a class instance with missing dependencies that should never have been created in the first place. You need to do custom checking inside the class to see if the actual dependency is available and throw an error if it is missing. You can prevent writing all this custom code by using the constructor dependency pattern.
Another issue is that it is harder to unit-test your class since you cannot set mocks for your individual dependencies so easily.
Read more on how to inject your dependencies in my answer to a similar question.
UPDATE
About the issue you encountered. Controller classes implement a ServiceLocatorAwareInterface and during construction of your controller classes the ControllerManager injects a ServiceLocator inside the class. This happens here in the injectServiceLocator method at line 208 in ControllerManager.php. Like #marcosh already mentioned in his answer, this might be a different service locator then you injected. In this injectServiceLocator method you also find the deprecation notice you mentioned in your question.
Yours is available in the __construct method because at that time (just after constructing the class) the variable is not yet overwritten. Later when you try to access it in your getServiceLocator method it is overwritten.