I have trouble with dependencies in my application in service layer.
I have following class:
<?php
class UserService{
private $userRepository;
private $vocationService;
private $roleService;
public function __construct(UserRepository $userRepository, VocationService $vocationService, RoleService $roleService)
{
$this->userRepository = $userRepository;
$this->vocationService = $vocationService;
$this->roleService = $roleService;
}
}
There are only three dependencies which I'm injecting.
Assume, I want to add next dependency, for example: NextService.
My constructor will grow again.
What if I wanted to pass more dependencies within constructor ?
Maybe should I solve this problem by passing IoC container and then get desirable class?
Here is an example:
<?php
class UserService{
private $userRepository;
private $vocationService;
private $roleService;
public function __construct(ContainerInterface $container)
{
$this->userRepository = $container->get('userRepo');
$this->vocationService = $container->get('vocService');
$this->roleService = $container->get('roleService');
}
}
But now my UserService class depends on IoC container which I'm injecting.
How to solve a problem following good practices?
Regards, Adam
Injecting the container as a dependency to your service is considered as a bad practice for multiple reasons. I think the main point here is to figure out why and then try to understand the problem that leads you to think about "injecting the container" as a possible solution and how to solve this problem.
In object oriented programming, it's important to clearly define the relations between objects. When you're looking at a given object dependencies, it should be intuitive to understand how the object behaves and what are the other objects it relies on by looking at its public API.
It's also a bad idea to let your object rely on a dependency resolver, In the example you shared your object can't live without the container which is provided by the DI component.
If you want to use that object elsewhere, in an application that uses another framework for example, you'll then have to rethink the way your object get its dependencies and refactor it.
The main problem here is to understand why your service needs all these dependencies,
In object-oriented programming, the single responsibility principle
states that every context (class, function, variable, etc.) should
define a single responsibility, and that responsibility should be
entirely encapsulated by the context. All its services should be
narrowly aligned with that responsibility.
Source: Wikipedia
Based on this definition, I think you should split your UserService into services that handle only one responsability each.
A service that fetch users and save them to your dababase for example
Another service that manages roles for example
... and so on
I agree that __construct can grow fairly easy.
However, you have a Setter DI at your disposal: http://symfony.com/doc/current/components/dependency_injection/types.html#setter-injection
Morover, there is a Property DI, but I wouldn't recommed it as ti leaves your service wide-open to manipulation: http://symfony.com/doc/current/components/dependency_injection/types.html#property-injection
You can abstract some of the commonly used services in one helper service and then just inject this helper into your other services. Also you can define some useful functions in this helper service. Something like this:
<?php
namespace Saman\Library\Service;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Form\FormFactory;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
use Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;
class HelperService
{
protected $translator;
protected $securityContext;
protected $router;
protected $templating;
protected $em;
public function __construct(
Translator $translator,
SecurityContext $securityContext,
Router $router,
TimedTwigEngine $templating,
EntityManager $em
)
{
$this->translator = $translator;
$this->securityContext = $securityContext;
$this->router = $router;
$this->templating = $templating;
$this->em = $em;
}
Getters ...
public function setParametrs($parameters)
{
if (null !== $parameters) {
$this->parameters = array_merge($this->parameters, $parameters);
}
return $this;
}
/**
* Get a parameter from $parameters array
*/
public function getParameter($parameterKey, $defaultValue = null)
{
if (array_key_exists($parameterKey, $this->parameters)) {
return $this->parameters[$parameterKey];
}
return $defaultValue;
}
}
Now imagine you have a UserService then you define it like this:
<?php
namespace Saman\UserBundle\Service;
use Saman\Library\Service\HelperService;
class UserService
{
protected $helper;
public function __construct(
Helper $helper,
$parameters
)
{
$this->helper = $helper;
$this->helper->setParametrs($parameters);
}
public function getUser($userId)
{
$em = $this->helper->getEntityManager();
$param1 = $this->helper->getParameter('param1');
...
}
This example was created for Symfony 4 but the principle should work in older versions.
As others have mentioned, it's good to engineer your application to limit the functional scope of each service and reduce the number of injections on each consuming class.
The following approach will help if you truely need many injections, but it's also a nice tidy way to reduce boilerplate if you are injecting a service in many places.
Consider a service App\Services\MyService that you wish to inject into App\Controller\MyController:
Create an 'Injector' trait for your service.
<?php
// App\Services\MyService
namespace App\DependencyInjection;
use App\Services\MyService;
trait InjectsMyService
{
/** #var MyService */
protected $myService;
/**
* #param MyService $myService
* #required
*/
public function setMyService(MyService $myService): void
{
$this->myService = $myService;
}
}
Inside your controller:
<?php
namespace App\Controller;
class MyController
{
use App\DependencyInjection\InjectsMyService;
...
public myAction()
{
$this->myService->myServiceMethod();
...
}
...
}
In this way:
a single line of code will make your service available in any container managed class which is super handy if you're using a service in many places
it's easy to search for your injector class to find all usages of a service
there are no magic methods involved
your IDE will be able to auto-complete your protected service instance property and know it's type
controller method signatures become simpler, containing only arguments
If you have many injections:
<?php
namespace App\Controller;
use App\DependencyInjection as DI;
class SomeOtherController
{
use DI\InjectsMyService;
use DI\InjectsMyOtherService;
...
use DI\InjectsMyOtherOtherService;
...
}
You can also create an injector for framework provided services, e.g. the doctrine entity manager:
<?php
namespace App\DependencyInjection;
use Doctrine\ORM\EntityManagerInterface;
trait InjectsEntityManager
{
/** #var EntityManagerInterface */
protected $em;
/**
* #param EntityManagerInterface $em
* #required
*/
public function setEm(EntityManagerInterface $em): void
{
$this->em = $em;
}
}
class MyClass
{
...
use App\DependencyInjection\InjectsEntityManager;
A final note: I personally wouldn't try to make these injectors any smarter than what I've outlined. Trying to make a single polymorphic injector will probably obfuscate your code and limit your IDE's ability to auto-complete and know what type your services are.
Related
I wondered why it is possible to access to the private properties and methods of a PHP Trait in a class that uses that Trait.
Here is my PHP Trait: BulkMessageMembers.php:
<?php
namespace App\Traits;
trait BulkMessageMembers
{
private string $endpoint;
private string $text;
private array $phone_numbers;
private string $server_response;
public bool $sms_status = false;
public int $user_id;
}
And my PHP class (This is only a portion of code.) SMSJsonBulkMessageService.php that uses my Trait:
<?php
namespace App\Services;
use App\Interfaces\SMSGateway;
use App\Models\Balance;
use App\Models\Message;
use App\Models\MessageSetting;
use App\Models\UserMessageSetting;
use App\Traits\BulkMessageMembers;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Support\Collection;
class SMSJsonBulkMessageService implements SMSGateway
{
use BulkMessageMembers;
public function __construct(
private readonly UserMessageSetting $preference,
private readonly Collection $messages, private readonly MessageSetting $setting
)
{
$this->prepare();
}
private function prepare(): void
{
$this->endpoint = $this->preference->messageProvider->bulk_endpoint;
$this->messages->each(function ($message) {
$this->phone_numbers[] = sprintf('243%d', $message->phone_number);
});
$this->text = $this->messages->first()->description;
}
/**
* #throws ConnectException
* #return void
*/
public function send(): void
{
//
}
public function update(): void
{
//
}
}
Can someone explain to me why from my PHP class I access the private properties of a Trait? I know trait is not a class per se, but its main interest is to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
Need clarification please.
A common explanation of traits is that they are "compiler-assisted copy-and-paste": when you incorporate a trait into a concrete class, the contents of that trait are inserted into the class definition as though you had written them by hand.
No distinction is maintained between members inserted by the trait, and members written directly in the class definition; and no relationship is maintained between the class and the trait the members were copied from. In some cases, there are additional constraints checked by the compiler, or renaming that is performed as part of the use statement, but in the simple case, it is exactly as though you had copied the code from one file to the other.
In your case, the code that is compiled is essentially this:
<?php
namespace App\Services;
use App\Interfaces\SMSGateway;
use App\Models\Balance;
use App\Models\Message;
use App\Models\MessageSetting;
use App\Models\UserMessageSetting;
use App\Traits\BulkMessageMembers;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Support\Collection;
class SMSJsonBulkMessageService implements SMSGateway
{
private string $endpoint;
private string $text;
private array $phone_numbers;
private string $server_response;
public bool $sms_status = false;
public int $user_id;
public function __construct(
private readonly UserMessageSetting $preference,
private readonly Collection $messages, private readonly MessageSetting $setting
)
{
$this->prepare();
}
private function prepare(): void
{
$this->endpoint = $this->preference->messageProvider->bulk_endpoint;
$this->messages->each(function ($message) {
$this->phone_numbers[] = sprintf('243%d', $message->phone_number);
});
$this->text = $this->messages->first()->description;
}
/**
* #throws ConnectException
* #return void
*/
public function send(): void
{
//
}
public function update(): void
{
//
}
}
Looking at that code, it should be no surprise that the prepare method has access to $this->phone_numbers.
Beyond #IMSoP's great answer, I think quoting the RFC that introduced the feature brings clarity, specifically the last sentence:
The Flattening Property
As already mentioned, multiple inheritance and Mixins are complex mechanisms. Traits are an alternative which have been designed to impose no additional semantics on classes. Traits are only entities of the literal code written in your source files. There is no notion about Traits at runtime. They are used to group methods and reuse code and are totally flattened into the classes composed from them. It is almost like a language supported and failsafe copy'n'paste mechanism to build classes.
I have a repository class called EmailRepository
class EmailRepository extends EntityRepository implements ContainerAwareInterface { ... }
I need to get a parameter injected into this repository class but I dont know how...
This is what I currently have inside of the repository, which is being called from my controller:
Controller:
$em->getRepository(Email::class)->getEmailApi();
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface {
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
echo $this->container->getParameter('email_api');
}
}
I always get this error:
Call to a member function getParameter() on null
The parameter is not null, it does have a value. I know it's telling me that $this->container is null. How do I fix this?
If I run this inside of my controller, it works fine and returns Google
echo $this->getParameter('email_api');
Inject container not a good idea. Try this
services.yaml
App\Repository\EmailRepository:
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository
{
protected $emailApi;
public function __construct(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}
Or via setter injection
services.yaml
App\Repository\EmailRepository:
calls:
- method: setEmailApi
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface
{
protected $emailApi;
public function setEmailApi(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}
Your original code is not going to work because there is nothing calling EmailRepository::setContainer. Furthermore, using ContainerAware and injecting the full container is discouraged.
Fortunately, the Doctrine bundle has a new base repository class that the entity manager can use to pull the repository from container and allow you to inject additional dependencies as needed. Something like:
namespace App\Repository;
use App\Entity\Email;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class EmailRepository extends ServiceEntityRepository // Different class to extend from
{
private $emailApi;
public function __construct(RegistryInterface $registry, ParameterBagInterface $parameterBag)
{
parent::__construct($registry, Email::class);
$this->emailApi = $parameterBag->get('email_api');
}
So in this case we inject all the parameters and then store the ones we need.
Even injecting the parameter bag is a bit frowned upon. Better to inject individual parameters though this takes just a bit more configuration as we need to use services.yaml to explicitly inject the needed parameters:
public function __construct(RegistryInterface $registry, string $emailApi)
{
parent::__construct($registry, Email::class);
$this->emailApi = $emailApi;
}
#services.yaml
App\Repository\EmailRepository:
$emailApi: 'email_api_value'
I'd like to retrieve my module configuration from a controller in Zend Framework 3.
I searched, and it seems that the standard way to do this in ZF2 is to use
$this->getServiceLocator()
to access the configuration in module.config.php.
However, this won't work in ZF3, as there is no getServiceLocator() method.
What is the standard way to achieve this?
Don't know if you found an answer, as there are different solutions as tasmaniski wrote. Just in case, let me share one that would have helped me a lot when I started to play with ZF3:
MyControllerFactory.php
<?php
namespace My\Namespace;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use DependencyNamespace\...\ControllerDependencyClass; // this is not a real one of course!
class MyControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
* #return AuthAdapter
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Get config.
$config = $container->get('configuration');
// Get what I'm interested in config.
$myStuff = $config['the-array-i-am-interested-in']
// Do something with it.
$controllerDepency = dummyFunction($myStuff);
/*...the rest of your code here... */
// Inject dependency.
return $controllerDepency;
}
}
MyController.php
<?php
namespace My\Namespace;
use Zend\Mvc\Controller\AbstractActionController;
use DependencyNamespace\...\DependencyClass;
class MyController extends AbstractActionController
{
private $controllerDepency;
public function __construct(DependencyClass $controllerDepency)
{
$this->controllerDepency = $controllerDepency;
}
/*...the rest of your class here... */
}
You need to inject your dependencies through service manager.
Basicly you need to create 2 class Controller and ControllerFactory that will create Controller with all dependencies.
I have a problem with an autocompletion in PhpStorm...
class Main
{
use Something;
/**
* #var SplObjectStorage
*/
private $container;
public function __construct()
{
$this->container = new SplObjectStorage();
}
public function addSth()
{
$this->add();
}
}
trait Something
{
public function add()
{
$this->container->attach(new stdClass());
}
}
$m = new Main();
$m->add();
var_dump($m);
Everything works fine but $this->container->attach(new stdClass()); throws that method attach is not found... Anyone can help? I think that properly configured PHPDoc should help.
The Trait has no way of knowing what type its $container is. In your example it is SplObjectStorage, but what if it isn't?
You need to place $container inside the Trait as well and declare it as SplObjectStorage. Then it should work. This way, you'll also be sure that whoever is declaring that Trait actually has a $container to have it work on.
trait Something {
/**
* #var SplObjectStorage
*/
private $container;
...
I suppose you can force the issue:
public function add()
{
/**
* #var $cont SplObjectStorage
*/
$cont = $this->container;
$cont->attach(new stdClass());
}
There's a couple of other ways to make this work.
Define $container inside the trait (as #Iserni suggested), but define the variable itself. This actually makes more sense to define it within the trait anyways, since the trait methods actually rely on it.
trait Something {
/** #var \SplObjectStorage */
protected $container;
public function add() {
$this->container->attach(new stdClass());
}
}
Pass it as an argument in your function
public function add(\SplObjectStorage $container) {
$container->attach(new stdClass());
}
PHPStorm has to have a way to refer back to the class to do things like autocomplete. Your trait cannot inherit its docs from the calling class. Your class, however, can inherit docs from the included trait.
This question can be viewed through a prism of ZF2 + Doctrine + MVC programming practices, or it can be viewed through just an OOP perspective.
My concern is about Separation of Concerns, and on removing dependencies.
I am using code in my controllers that goes something like this:
class MyController
{
private $em; //entityManager
function __construct()
{
$this->em = DoctrineConnector::getEntityManager();
}
function indexAction()
{
//Input
$inputParameter = filter_input(...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $services));
return $view;
}
}
I am not entirely comfortable with the above and wanted a second opinion. For one, my EntityManager is part of the class, so my class is cognizant of the entity manager construct, when I think it should not be a part of the controller. Do I perhaps use a Factory or Builder design pattern to help me create MyController class?
If I do, I can move my em (entityManager) construct into the Factory pattern and create and populate my MyController inside the Factory. Then, the MyController can have a private variable $services instead.
i.e.
class MyController
{
private $services;
function setServices($services)
{
$this->services = $services;
}
function indexAction()
{
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $this->services));
return $view;
}
}
class MyFactoryMethod
{
function createMyController()
{
//Input
$inputParameter = filter_input(INPUT_GET...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//create and return MyController instance
$controller = new MyController();
$controller->setServices($services);
return $controller;
}
}
I typically tried to do this PHP's mysql extension to remove dependency on data out of my various objects. I am using Doctrine2 now which is an ORM, and wondering if I should keep doing the same thing (namely preferring 2nd example rather than the first...
Question:
I can write code both ways. It works essentially the same. My question is -- is the code, as it is written in my 2nd example preferred more than the code as it is written in my first?
Notes / Clarifications:
In my case variable $services is a domain-specific variable (not ZF2's ServiceLocator). i.e. think of MyController as a controller for business-specific "services".
I am not harnessing full power of ZF2 with configs, routers, events, and everything. I am using ZF2 modules on an existing legacy codebase on as-needed basis.
When your controller has hard dependencies I would suggest to use the common ZF2 solution by creating the controller and injecting the dependency in a factory instance and registering the controller under the 'factories' key in your 'controllers' config array.
In your module.config.php
'controllers' => array(
'factories' => array(
'Application\Controller\MyController' => 'Application\Controller\MyControllerFactory'
)
)
In your controller I would set hard dependency in the __construct method. Like this you prevent the controller from ever being instantiated without your dependencies (it will throw an exception).
Never inject something like $services (if this is a ServiceLocator) from which you will pull the actual dependencies since it is not clear what the class actually needs. It will be harder to understand for other developers and it is also hard to test since you cannot set mocks for your individual dependencies so easily.
Your Controller class:
<?php
namespace Application\Controller;
use Doctrine\ORM\EntityManager;
class MyController
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
*
*/
function indexAction()
{
//Do stuff
$entityManager = $this->getEntityManager();
}
/**
* #return EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
}
Your Factory:
<?php
namespace Application\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Doctrine\ORM\EntityManager;
class MyControllerFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return MyController
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var EntityManager $entityManager */
$serviceManager = $serviceLocator->getServiceLocator()
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
$myController = new MyController($entityManager);
return $myController;
}
}
There are two different approaches to this problem that are provided by ZF2.
Use the ServiceLocator to retrieve the EntityManager via a Factory.
In Module.php, add an anonymous function or Factory.
public function getServiceConfig()
{
return [
'factories' => [
'Doctrine\ORM\EntityManager' => function (ServiceManager $sm) {
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
return $entityManager;
}
],
],
}
In your Controller
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
Create an Initializer and AwareInterface to inject the EntityManger into your controllers.
The AwareInterface can be added to any class which is initialized by the ServiceManager.
interface EntityManagerAwareInterface
{
/**
* Set EntityManager locator
*
* #param EntityManager $entityManager
*/
public function setEntityManager(EntityManager $entityManager);
/**
* Get service locator
*
* #return EntityManager
*/
public function getServiceLocator();
}
The Initializer is run when services are initialized by the ServiceManager. A check is performed to so if $instance is a EntityManagerAwareInterface.
use Application\EntityManager\EntityManagerAwareInterface;
use Zend\ServiceManager\InitializerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class EntityManagerInitializer implements InitializerInterface
{
/**
* Initialize
*
* #param $instance
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if ($instance instanceof EntityManagerAwareInterface) {
$entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
$instance->setEntityManager($entityManager);
}
}
}
Next add the Initializer to Module.php
public function getServiceConfig()
{
return [
'initializers' => [
'entityManager' => new EntityManagerInitializer(),
],
],
}
The advantage of going the Initializer route is there is a one time setup. Any class that implements the EntityManagerAwareInterface will have the EntityManager injected when the class is initialized.