I have a factory that implements FactoryInterface, like:
class SomeFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
// the code to create SomeClass instance
}
}
Now, all the code is inside the createService method (multiple ifs, some queries, etc.) which makes the method long and difficult to follow. I'd like to refactor the code by extracting the some pieces of code into separate methods. Problem is for my case, I end up passing the instance of $serviceLocator->getServiceLocator() in each of these methods, which is no problem really, but feels ugly.
I was wondering if there's an elegant way of maybe assigning the attribute of SomeFactory $serviceLocator->getServiceLocator() than just passing it in every extracted method.
I think you don't really understand Factories usage.
A factory is ONLY used to inject dependencies in another object, like a Controller or a Service.
So, sure, it depends of what you want to do, but I think you want to create a Service to do some think.
Example :
class MyService implements ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait; // used to inject SL getter and setter in your code
public function myMethod1()
{
$formElementManager = $this->getServiceLocator()->get('formElementManager');
// ... etc ...
}
public function myMethod9999() ......
}
class MyServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$myService = new MyService();
$myService->setServiceLocator($serviceLocator);
$myService->setAnotherProperty(...);
return $myService;
}
}
If ServiceLocator is really needed in your MyService() class, it's recommended to inject it in the __construct() method
Related
I am relatively new to Symfony (version 4) and trying to implement the __construct method for dependency injection.
Currently, I am "injecting" dependencies via my own implementation (before I was aware of the __construct method) like so:
routes.yaml
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch::init
requirements:
req: ".+"
/fetch route calls the init() method, which serves as the constructor.
Controller Class
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use App\Services\Utilities; // a bunch of useful functions
class Fetch extends BaseController {
private $u;
public function init(Utilities $u) {
$this->u = $u; // set the $u member with an instance of $u
}
private function do_fetch(){
$this->u->prettyprint('hello service'); // use one of $u's methods
}
}
If you would indulge me, I came up with this ad-hoc scheme before reading the docs, which detail this almost exactly (I get a cookie).
The one difference is that the docs use __construct() in place of my init() method. The following is an example from the doc page linked above:
// src/Service/MessageGenerator.php
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function getHappyMessage()
{
$this->logger->info('About to find a happy message!');
// ...
}
}
But when I swap init() for __construct(), and update the routes.yaml, I get an error.
// .....
class Fetch extends BaseController {
private $u;
public function __construct(Utilities $u) {
$this->u = $u; // set the $u member with an instance of $u
}
// ....
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch::__construct
requirements:
req: ".+"
Its asking me to provide an argument to __construct since that method takes one ($u) but this was not the case when init() was acting as the constructor.
Moreover, I feel like since the __construct() method is a built-in hook, Symfony should know to use it without my having to explicitly tell it to in routes.yaml. However, excluding it throws an error as well.
routes.yaml (__construct not explicitly indicated)
fetch:
path: /fetch/{req}
controller: App\Controller\Fetch
requirements:
req: ".+"
What am I missing here?
__construct is a magic method in PHP. The problem with your init method is that it does not enforce that the object must have an instance of the object you need in order to be built. Sometimes an object property will not be needed. In this case, I recommend creating a setter as a way to optional set that property.Try to make your class properties private, and only allow them to be mutated or retrieved through setters and getters...this will provide a standard API to your obejct, and avoid random state manipulation.
You can use the DIC in Symfony's router to construct your controller instead of extending the base controller class by registering your controllers as services. This greatly decouples you code and allows all kinds of additional flexibility. You should always favor composition over inheritance.
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.
EDITED (Code is updated and working for others)
For the overall idea of what's happening.
I'm trying to access post data from the view in the controller, without refreshing the page.
To do this I am executing the page controller by using a ViewHelper to call the Service below which then forwards back to the controller; afterwards I can manage the posted data in the page controller.
Everything works except the last step which is the forward(), I receive the error Call to undefined method AlbumModule\Service\postAlbumService::forward()
I understand I must implement the ServiceLocatorAwareInterface in order to use the forward() class, but what I've written doesn't seem to work.
<?php
namespace AlbumModule\Service;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class postAlbumService implements
ServiceLocatorAwareInterface
{
protected $services;
public function __construct() {
echo '<script>console.log("postAlbumService is Started")</script>';
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function test(){
$cpm = $this->getServiceLocator()->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
echo '<script>console.log("postAlbumService TEST() is Started")</script>';
return $fwd->dispatch('newAlbum', array('action' => 'submitAlbum'));
}
}
It seems as though I'm just having a dependency issue with the forward() class, but I'm not sure what the issue is.
EDIT-
Here is how I am calling the postAlbumService from the viewHelper
<?php
namespace AlbumModule\View\Helper;
use Zend\View\Helper\AbstractHelper;
class invokeIndexAction extends AbstractHelper
{
protected $sm;
public function test()
{
$this->sm->getServiceLocator()->get('AlbumModule\Service\postAlbumService')->test();
}
public function __construct($sm) {
$this->sm = $sm;
}
}
Is there any way to call a specific class in the service being requested, after the dependencies are injected into the service?
You're doing a couple of things wrong and you're misunderstanding some things...
First of all, forward() is a ControllerPlugin. You'll gain access to this method by accessing said manager via the ServiceLocator. An example could be this:
$cpm = $serviceLocator->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
return $fwd->dispatch('foo/bar');
Now, to get the ServiceLocator into any of your Service-Classes you need Dependency Injection. One of the ways is to implement the ServiceLocatorAwareInterface. The ServiceManager of ZF2 has so called Listeners. These Listeners check for implemented interfaces and stuff like this. Whenever it finds a match, it injects the required dependencies via the interfaces given functions. The workflow looks like this:
ServiceManager get('FooBar');
$ret = new FooBar();
foreach (Listener)
if $ret instanceof Listener
doInjectDependenciesInto($ret)
end
end
return $ret
Now what does this tell you. This tells you, that within the __construct() of any of your classes NONE of your required dependencies are actually there. They only get injected AFTER the class/service has been instantiated.
On a last side-note, the given code example doesn't really make much sense ;) No matter what ServiceAction i'd like to access, you'd always return me to the "newAlbum" action...
I got this problem I have a method that is repetitive in all the Repositories, for example this method.
function getAllOrderedBy($column) {
$qb = $this->createQueryBuilder('ac')
->select('ac')
->orderBy('ac.' . $column);
return $qb->getQuery()->getResult();
}
I want to extract it in another superclass, OrderedRepository for example and use it as the base class for all the other repositories.
Now the problem is how to do that ?
I tried to instantiate EntityRepository in the constructor of the OrderedRepository,
something like this, but also instantiating there all the internal objects, needed for other stuff, but it didn't really worked, and I felt it is the wrong path to follow.
function __construct() {
parent::__construct();
$this->blabla_option = "instantiated";
}
Could you please give an example of correct extending of EntityRepository so than this extended class could serve as a base class for other repositories ?
P.S. I'm a begginer in PHP so please excuse me if I hurt your feelings with my unawareness.
This is more a Doctrine2 thing.
Assuming you are using annotations for your doctrine mapping, you have to declare which repository class you are using in the Entity:
/**
* #ORM\Entity(repositoryClass="Fully\Qualified\Namespace\To\MyRepository")
*/
class MyEntity { }
as explained here: http://symfony.com/doc/2.0/book/doctrine.html#custom-repository-classes .
Then, you can code this custom MyRepository class, using standard class inheritance.
You could imagine something like that:
class OrderedRepository extends EntityRepository
{
// some extra methods...
}
class MyRepository extends OrderedRespository {}
Finally, if you want to override the __constructor of your repository, you have to initailize the parent constructor with the same arguments:
public function __construct($em, Mapping\ClassMetadata $class)
{
parent::__construct($em, $class);
// some extra stuff
}
For objects which compose another object as part of their implementation, what's the best way to write the unit test so only the principle object gets tested? Trivial example:
class myObj {
public function doSomethingWhichIsLogged()
{
// ...
$logger = new logger('/tmp/log.txt');
$logger->info('some message');
// ...
}
}
I know that the object could be designed so that the logger object dependency could be injected and hence mocked in a unit test, but that's not always the case - in more complicated scenarios, you do need to compose other objects or make calls to static methods.
As we don't want to test the logger object, only the myObj, how do we proceed? Do we create a stubbed "double" with the test script? Something like:
class logger
{
public function __construct($filepath) {}
public function info($message) {}
}
class TestMyObj extends PHPUnit_Framework_TestCase
{
// ...
}
This seems feasible for small objects but would be a pain for more complicated APIs where the SUT depended on the return values. Also, what if you want to test the calls to the dependency object in the same was you can with mock objects? Is there a way of mocking objects which are instantiated by the SUT rather than being passed in?
I've read the man page on mocks but it doesn't seem to cover this situation where the dependency is composed rather than aggregated. How do you do it?
Following troelskn advise here's a basic example of what you should do.
<?php
class MyObj
{
/**
* #var LoggerInterface
*/
protected $_logger;
public function doSomethingWhichIsLogged()
{
// ...
$this->getLogger()->info('some message');
// ...
}
public function setLogger(LoggerInterface $logger)
{
$this->_logger = $logger;
}
public function getLogger()
{
return $this->_logger;
}
}
class MyObjText extends PHPUnit_Framework_TestCase
{
/**
* #var MyObj
*/
protected $_myObj;
public function setUp()
{
$this->_myObj = new MyObj;
}
public function testDoSomethingWhichIsLogged()
{
$mockedMethods = array('info');
$mock = $this->getMock('LoggerInterface', $mockedMethods);
$mock->expects($this->any())
->method('info')
->will($this->returnValue(null));
$this->_myObj->setLogger($mock);
// do your testing
}
}
More information about mock objects can be found in the manual.
As you seem to be aware already, Concrete Class Dependencies makes testing hard (or outright impossible). You need to decouple that dependency. A simple change, that doesn't break the existing API, is to default to the current behaviour, but provide a hook to override it. There are a number of ways that this could be implemented.
Some languages have tools that can inject mock classes into code, but I don't know of anything like this for PHP. In most cases, you would probably be better off refactoring your code anyway.
Looks like I misunderstood the question, let me try again:
You should use the singleton pattern or a factory for the logger, if it's not too late already:
class LoggerStub extends Logger {
public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();
If you can't change the code, you could use a catch-all class that is overloading __call()
class GenericStub {
public function __call($functionName, $arguments) {}
}
There is actually a reasonably new extension for PHP class overloading released by the same guys that build PHPUnit. It lets you override the new operator in cases where you can't refactor the code, unfortunately it isn't that simple to install on Windows.
The URL is http://github.com/johannes/php-test-helpers/blob/master/