Alternatives to Zend Registry for ZF2 - php

I have just started using ZF2 and am really enjoying it.
One thing that puzzles me a bit is the absence of a Registry component. I realise that the Service Manager makes the Registry obsolete in most cases. I rely on it heavily and its great.
But from time to time I find myself needing access to a 'global' object, and I don't have access to the Service Manager. For example, in my Domain\User object I need access to a Zend\Log.
I don't want to make the Service Manager available in my Domain objects, since they are beautiful and pristine, and unadulterated by such considerations. I could 'new' a log instance whenever required, but I do it so often I'd rather have a preconfigured instance to hand. I could wrap it in a singleton, but that seems like a backward step. I could create my own mini-registry, but if that was a good idea, I'm sure the Zend guys would have left such a component in place.
So, what other options are there?
EDIT:
So, could I use Zend DI perhaps? I see this question partially covers it, Configuring class alias for using with Zend\Di\Di

You are exactly addressing the problem of dependency injection. I have blogged about this topic before, how to refactor towards dependency injection and what to do if you have soft dependencies like a Logger.
The idea is you inject the pre-configured logger. When your Domain\User object is created, the logger is injected. This makes the Domain\User object only dependent on the logger without having the knowledge how to create the logger. It's even better if you rely on a Logger interface, so you can swap to any logger implementation you want.
Example
As an example, I assume you are using Zend\Log. You have a Logger like Zend\Log\Logger with various writers attached. The logger implements Zend\Log\LoggerInterface.
Your Domain\User class:
namespace Domain;
Zend\Log\LoggerInterface;
class User
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
$this->logger->info('Do Something');
//
}
}
For Zend Framework, you should work with factories to inject this logger into your objects. The best way is to define the Logger as a service first, as you could reuse the logger for other objects as well.
Note I use the Service Manager and not Zend\Di here. Zend\Di is obsolete and replaced by the faster and more flexible Zend\ServiceManager. Both can achieve dependency injection.
The factory for you logger:
namespace MyModule\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Log\Logger;
class LoggerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$logger = new Logger;
// do more with $logger here
return $logger;
}
}
Then register this factory to create the service "logger" for you. In your module.config.php:
'service_manager' => array(
'factories' => array(
'logger' => 'MyModule\Factory\LoggerFactory',
),
),
Now logger is available in your service manger. For your domain model, do the same. Create a factory first:
namespace MyModule\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Domain\User;
class UserFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$logger = $serviceLocator->get('logger');
$user = new User($logger);
return $user;
}
}
Then register this one too:
'service_manager' => array(
'factories' => array(
'logger' => 'MyModule\Factory\LoggerFactory',
'Domain\User' => 'MyModule\Factory\UserFactory',
),
),
If you have access to the service locator, you can get the Domain\User and the logger is automatically injected:
$user = $sl->get('Domain\User');

Related

Inject container in controller class

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.

ZF2 Model used in many modules using zend global config

I have a Class object which is used in many modules in my zend structure :
/module/
--|Aplication
--|MyClassModule
----|config
----|src
------|Factory
------|Model
---------|> MyObjectClass.php
----Module.php
--|AnotherModule
So my idea is to use this MyObjectClass.php in other modules so I can avoid duplication and have its own configuration. So far, for this is ok, however I want to get the variables set from my config/autoload files injected in this class but I don't know how.
How can I load this config data into my class model? Which is the best approach ? I can load it by accessing this directly but I don't think this is very elegant
e.g: $configArray = require './config/autoload/config.local.php';
I am not very experienced with zend so I dont know where to start with. I have seen many tutorials of how to do this via controllers, views.. etc but not in specific classes.
Thank you.
All config files are merged into one config, when your ZF2 application is bootstrapped. That includes local.php, global.php from config/autoload and all used modules' module.config.php. With a bit of more research, you can overwrite the standard loading, e.g. loading custom configs.
After bootstrapping, your are able to access the config from the ServiceManager. There are preserved keys for some ZF2-specific configs, service_manager, etc.
$serviceManager->get('config');
There is a "standard" service pattern in ZF2: Factory. This can be applied for Controllers, Services. What ever you want.
namespace Application\Factory;
use Application\Model\MyObjectClass;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyObjectFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
// get some config parameter, inject it into model
$config = $serviceLocator->get('config');
$myObjectClass = new MyObjectClass();
// ... e.g. $myObjectClass->setConfig($config);
return $myObjectClass;
}
}
It should be clear, what this factory is made for: create and return an instance of your custom object ;) You may configure your instance with some config params. With ServiceLocator as method param, you are able to access the config, other services etc.
Further, you have to register your own service/factory in the factories section of service_manager config in your module's module.config.php:
return array(
'service_manager' => array(
'factories' => array(
'MyObjectFactory' => 'Application\Factory\MyObjectFactory',
),
),
);
Now you should be able to access your factory, e.g. in an ActionController or wherever you have access to ServiceManager. That means, you can also access this factory from different modules.
public function someCustomAction() {
$myObjectClass = $this->getServiceLocator()->get('MyObjectFactory');
$myObjectClass2 = $this->getServiceLocator()->get('MyObjectFactory');
var_dump($myObjectClass);
var_dump($myObjectClass2);
if ($myObjectClass === $myObjectClass2) {
echo '<br />equal';
}
$myObjectClass = new MyObjectClass();
$myObjectClass2 = new MyObjectClass();
var_dump($myObjectClass);
var_dump($myObjectClass2);
}
Note:
Be aware, that ServiceManager returns the same instance of your object. So, that seems like what you ask for? In contrast, creating a new instance will create different objects.
Note 2:
Tested with ZF2 v2.4.9

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.

Laravel - Defining repository overrides

I am using Laravel 5.1 and have set up a Repository pattern. I have the concrete implementations of my repos injected into my controllers. I realize that your SUPPOSED to inject the interface but that over complicates my API and doesn't solve my issue. I have a client config that simply contains a string such as '' and I am already using that globally to use model overrides if they exist.
So, for example, if I have client 'yahoo' and they have an override in my Overrides/Yahoo/Models/User.php then it will use this User.php. Whether its an extension of the base User model or a whole new implementation is up to me.
I am trying to do the same thing for my repositories. I want to be able to put an override file in Overrides/Yahoo/Repos/UserRepository.php and on injection it will either use the base User repository or the override if it exists.
Any ideas of how I can accomplish this? I realize that you can inject a repository interface and use a binding but I want to do this globally. If you can tell me how I can abstract that functionality to automatically bind the correct implementation of the interface based on client config I would accept that.
You can resolve your concrete implementation by the configuration you use, but it requires that you use interfaces.
Example configuration (config/client.php):
<?php
return [
'users' => [
'repository' => \App\Overrides\Yahoo\Repos\UserRepository::class,
],
];
In your AppServiceProviders register method:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
UserRepositoryInterface::class,
config('client.users.repository')
);
}
In your Yahoo UserRepository, you can then inject the Yahoo User model directly:
<?php
namespace App\Overrides\Yahoo\Repos;
use App\Repos\UserRepositoryInterface;
use App\Overrides\Yahoo\Models\User;
class UserRepository implements UserRepositoryInterface
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
// TODO: Implement UserRepositoryInterface methods
}
Finally, in your UserController you can inject the UserRepositoryInterface and it will bind the concrete implementation based on your configuration:
<?php
namespace App\Http\Controllers;
use App\Repos\UserRepositoryInterface;
class UserController extends Controller
{
private $users;
public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
}
It might seem overkill at first, but once you set up everything it's pretty easy to add new overrides.
You could even create a base concrete implementation of the UserRepository and make every override repository inherit from it. This way you don't have to re-implement all methods required by the interface, but you stay flexible when the user repositories use different database technologies (SQL, MondoDB...)

Using Doctrine inside ZF2 Models

I'm working for the first time to get Doctrine working with a new ZF2 app. Doctrine is working fine if I call it within the controller (like every last tutorial out there has you do), however it doesn't make any sense performing business logic in the controller.
A few things I found suggests dependency injection passing the Entity Manager in from the controller, some suggest having your class implement ServiceLocatorAwareInterface.
My question is, how is anyone else using it within their models? Surely someone is using it the correct (MVC) way and not putting all of their business logic within their application controllers?
There are of course different solutions for this, but I personally use a Service layer. So for instance you would have a UserService which takes care of handling the business logic of User objects.
To allow the service to do its job you would inject its dependencies. Implementing ServiceLocatorAwareInterface is an option, but if you find yourself using getServiceLocator()->get('...') a lot it becomes a pita to write unit tests and injecting mock objects. A hybrid solution for that would be to have your service implement ServiceLocatorAwareInterface and have a getServiceA() and setServiceA() method where the getServiceA would look like:
if (!$this->serviceA)
{
$this->getServiceLocator()->get('ServiceA');
}
return $this->serviceA;
That way you can still inject a mock version of the dependency in your unit test.
I usually make a Service and instantiate it with a Factory in which I inject either the ServiceLocator or the EntityManager itself directly into the service. The typical folder structure would look something like
\src
\Module
\Controller
\Service
BusinessService.php
\Factory
BusinessServiceFactory.php
In your Module.php or the equivalent in module.config.php for that matter
function getServiceConfig() {
return array(
'factories' => array(
'service.business' => 'Module\src\Module\Factory\BusinessServiceFactory,
),
)
}
Then the actual Service and the Factory itself
BusinessServiceFactory.php
namespace Module\Factory\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class BusinessServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$service = new BusinessService($serviceLocator);
return $service;
}
}
BusinessService.php
namespace Module\Service;
use Zend\ServiceManager\ServiceLocatorInterface;
class BusinessService
{
/**
* #var Service locator
*/
protected $serviceLocator;
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
You can now define all your business logic in the BusinessService class. It is callable from your Controllers by this->serviceLocator->get('service.business')

Categories