Laravel Dependency Injection with inheritance - php

Let's say I have the following case:
<?php
abstract class Service {
protected $config;
public function __construct($config)
{
$this->config = $config;
}
}
class ClientService extends Service {
}
class ProductService extends Service {
}
Is it possible to register in my service provider the dependency injection for the Abstract parent class of my services ?
I have an API which is generated dynamically from a specification, and each one of those classes must extend the abstract Service so it can inherit for basic functionalities.
How can I Inject dependencies in my abstract service when I instantiate a child Service ?
EDIT: This question was specifically asked for Abstract class injection, without the possibility to bind the child classes which are generated automatically.

In your example, you have to manually pass the config object every time you instantiate from Service class or a child class.
So when you want to directly instantiate a child service, you could use something like, $cs = new ClientService(new Config());
However, you can use the real advantage of DI (since you are using Laravel), by type hinting the class name in the constructor like below.
public function __construct(\Config $config)
This way, if you do not pass a parameter when instantiating, it would by default create an object of the type-hinted class and inject it. So you could then use it like.
$cs = new ClientService();
This would inject a Laravel Config instance into the ClientService object.

There are two possible things you could do here. First, if $config is a class, then you can type hint it in the abstract class:
abstract class Service {
protected $config;
public function __construct(ClassName $config)
{
$this->config = $config;
}
}
Then every time the child classes get resolved via injection or by calling App::make('ClientService'), the config class will be injected.
If the config is not a class and can't be type hinted, you will have to bind the child classes into the container individually:
App::bind('ClientService', function () {
// Get $config from somewhere first
return new ClientService($config);
});
App::bind('ProductService', function () {
// Get $config from somewhere first
return new ProductService($config);
});
Then you will be able to call App::make('ClientService') or have it resolved via DI.

Related

Symfony setter injection for polymorphic service

I'm using Symfony 5.4 and PHP 7.4 with the default service configuration. I have an abstract service like this:
abstract class Parent
{
protected FooInterface $fooDependency;
public abstract function setFooDependency(FooInterface $fooDependency): void;
}
In my child classes, I want to inject different implementations of FooInterface for each child. The problem is that PHP rightly complains about the child method's declaration not being compatible if the argument's type is anything other than FooInterface:
class Child extends Parent
{
public abstract function setFooDependency(FooService $fooDependency): void {}
}
I can solve this by not defining setFooDependency() in the abstract parent, but that kind of defeats the purpose of having the abstract class in the first place (I want to enforce a contract!)
Another workaround is to implement setFooDependency() in the parent class (instead of making it abstract), then in services.yaml manually specify the argument for each child class:
services:
App\Service\Child:
calls:
- setFooDependency: [App\Service\FooService]
But I'm not fond of this, because anyone extending the abstract class has to magically know to go and add the service definition.
I'm hoping I'm missing an easy solution? What I want is to:
Enforce child classes having a setter method (or some other way to inject the dependency)
Allow each child class to inject a different implementation of FooInterface
Use autowiring so I don't clutter my services file or confuse future devs
I think it's easier using the constructor:
abstract class Parent
{
protected FooInterface $fooDependency;
publich function __construct(FooInterface $fooDependency) :void
{
$this->fooDependency = $fooDependency;
}
}
Then you can define each implementation in services.yml without worring about calling a method:
YourNamespace\Child:
class: YourNamespace\Child
arguments: [#your_foo_implementation_alias_for_this_child]
Create a parent class (for example FooObject) for FooInterface and FooService.
Make FooInterface and FooService ... extends from this new class
Define $fooDependency as FooObject
What I want is to:
Enforce child classes having a setter method (or some other way to inject the dependency)
Allow each child class to inject a different implementation of FooInterface
Use autowiring so I don't clutter my services file or confuse future devs
There are various methods to achieve polymorphism in PHP, but it seems that you may be able to resolve this issue by defining an abstract method to retrieve the FooInterface instance instead of a setter method. This would enable you to inject a concrete FooInterface implementation directly into your constructor while maintaining immutability.
interface FooInterface
{
public function getName(): string;
}
class ConcreteFoo implements FooInterface
{
public function getName(): string
{
return 'a string';
}
}
abstract class Father
{
public function doSomething(): void
{
$name = $this->getFoo()->getName();
// ...
}
abstract protected function getFoo(): FooInterface;
}
class Child extends Father
{
private ConcreteFoo $foo;
public function __construct(ConcreteFoo $foo)
{
$this->foo = $foo;
}
protected function getFoo(): ConcreteFoo
{
return $this->foo;
}
}
By using this approach, your parent class will not depend on a property, but rather on a method that every child class must implement. This will allow for greater flexibility, maintainability, and the DI autowiring won't be a problem.

Laravel DI with interfaces

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)
});
}

zf2 controller factory serviceLocator

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.

How to integrate pimple in a custom mvc framework?

I have a basic mvc like framework, and I would like to use pimple for dependance injection, but I don't know how to use it inside the framework. This is my app structure.
x-framework
- config
- app
controller
homeController.php
- core
- vendor
pimple
lib
pimple.php
- public
Now, in homeController.php I would like to use Pimple, but without actually doing new Pimple as seen in this example.
use vendor\pimple;
class homeController
{
function index(){
$app = new Pimple();
$app['orm'] = $app->share({ return new vendor\orm; });
$orm = $app['orm'];
$orm->table('foo');
$orm->findFirst(['name'=>'john']);
}
}
It seems as seen in this example, it would be a very cumbersome task to initialize the pimple class on every controller. How is this done correctly?
My answer was not relevant, though the principle of abstract classes stays interesting. Now:
I would like to use Pimple, but without actually doing new Pimple as seen in this example.
At some point you have to instantiate an object, if you want to use it.
Pimple uses a container to store and retrieve services and parameters:
$container = new \Pimple\Container();
// define some services
$container['session_storage'] = function ($c) {
return new SessionStorage('SESSION_ID');
};
this exemple from the doc defines an anonymous function which returns a session storage object
integrating a container
Pimple, or any container, can be made available using the dependency injection pattern.
either pass it as a parameter to the index
function index(\Pimple $app){
or pass it to homeController's constructor
function __construct(\Pimple $app){
$this->app = $app;
then use it as a property or a variable
$orm = $app['orm']; // through index() parameters
$orm = $this->app['orm']; // through constructor
abstract classes allow you to define a method for every extending classes, or forcing every extending classes to define a method.
here, we define a constructor for every extending classes, typehinting the Pimple class so that php will ensure your controller receives a real pimple object
abstract class Pimpleized {
function __construct(\Pimple $pimple) {
$this->app = $pimple;
}
}
then your controller
class homeController extends Pimpleized {
function foo() {
$this->app->accessSomePimpleMethod();
}
}
that way, you only have to create your Pimple object once, then pass it to your controllers:
$pimp = new Pimple();
$controller = new homeController($pimp);
Just extend HomeController class with pimple
class HomeController extends Pimple {
public function __construct() {
$this['orm.class']= 'vendor\orm';
$this['orm'] = $this->share(function($c){ return new $c['orm.class']; });
}
}
//and use it directly just after instanciation
$controller = new HomeController();
// you can modify parameters if you need
$controller['orm.class'] = 'myothervendor\orm';
//And get class
$orm = $controller['orm'];
$orm->table('foo');
$orm->findFirst(['name'=>'john']);
i hope it's you want :) cheers

Laravel not finding IoC binding in controller

I have a custom class App/Http/Responder, which had a few methods to build a specific JSON response back in my application. I want to test my controller in isolation, so I'm trying to inject my dependencies via the constructor.
My plan was to simply create a service provider, attach bind it to the $app and then, as per the docs, let it be automatically resolved:
public function register()
{
$this->app->bind('responder', function()
{
return new App\Http\Responder($this->app['cache'], $this->app['app'], new JsonResponse, $this->app['config']);
});
}
I then add this to my config/app.php.
Okay, so now my Responder and it's dependancies are bound to the app, as responder.
Now I thought I'd be able to inject Responder into my controller constructor, and Laravel would be able to automatically resolve this from the IoC container:
class AreasController extends BaseController {
protected $responder;
public function __construct(Responder $responder)
{
$this->responder = $responder;
}
However I get Class Responser does not exist.
The only way I can get it working, without using the App::make() Facade, is to inject the app into my controller:
use Illuminate\Foundation\Application as App;
class AreasController extends BaseController {
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
I can then do $this->app['responder']->method().
Obviously I'm missing something, but I want to keep away from using Facades in my app so I can test.
If you want to type hint classes to be resolved in the IOC container, you should bind the actual class name with namespace:
$this->app->bind('App\Http\Responder', function()
{
return new App\Http\Responder($this->app['cache'], $this->app['app'], new JsonResponse, $this->app['config']);
});
Technically the container would still resolve this class, because it's a concrete class that can be found, but the way you're doing allows to inject other IOC-bound resources, which is a good practice.
Then, when you wish to have this class injected for you, type hint the full path to the class as you normally would:
use App\Http\Responder;
class AreasController extends BaseController {
protected $responder;
public function __construct(Responder $responder)
{
$this->responder = $responder;
}
}
Also, for what it's worth, your error indicates that you misspelled "Responder" as "Responser".

Categories