I know that this has been covered extensively in other threads, but I'm struggling to work out how to replicate the effect of $this->getServiceLocator() from ZF2 controllers in ZF3 ones.
I have tried creating a factory using the various other answers and tutorials that I've found here and elsewhere, but ended up in a mess with each of them, so I'm pasting my code as it was when I started in the hope that someone can point me in the right direction?
From /module/Application/config/module.config.php
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
],
],
From /module/Application/src/Controller/IndexController.php
public function __construct() {
$this->objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$this->trust = new Trust;
}
You can not use $this->getServiceLocator() in controller any more.
You should add one more class IndexControllerFactory where you will get dependencies and inject it in IndexController
First refactor your config:
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\IndexControllerFactory::class,
],
],
Than create IndexControllerFactory.php
<?php
namespace ModuleName\Controller;
use ModuleName\Controller\IndexController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container,$requestedName, array $options = null)
{
return new IndexController(
$container->get(\Doctrine\ORM\EntityManager::class)
);
}
}
At the end refactor you IndexController to get dependencies
public function __construct(\Doctrine\ORM\EntityManager $object) {
$this->objectManager = $object;
$this->trust = new Trust;
}
You should check official documentation zend-servicemanager and play around a little bit...
Whilst the accepted answer is correct, I will implement mine a bit differently by injecting the container into the controller and then get other dependencies in constructor like so...
<?php
namespace moduleName\Controller\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use moduleName\Controller\ControllerName;
class ControllerNameFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new ControllerName($container);
}
}
Your controller should look something like this:
namespace ModuleName\Controller;
use Doctrine\ORM\EntityManager;
use Zend\ServiceManager\ServiceManager;
class ControllerName extends \App\Controller\AbstractBaseController
{
private $orm;
public function __construct(ServiceManager $container)
{
parent::__construct($container);
$this->orm = $container->get(EntityManager::class);
}
In your module.config, be sure to register the factory like so:
'controllers' => [
'factories' => [
ControllerName::class => Controller\Factory\ControllerNameFactory::class,
],
Related
I'd like to inject an array of objects that implement a common interface into one of my services. I am using zend servicemanager as the DI container. I have been reading the docs for quite a bit now and it seems to me that AbstractPluginManager is the way to go. I haven't been able to make it work though.
Is there an example using an AbstractPluginManager + Zend Expressive 3 that I can take a look at?
My ultimate goal is to dynamically inject all registered classes that implement a common interface into my service.
Example:
interface I{}
class A implements I{}
class B implements I{}
class C{}
MyService
__construct(array Iimplementations){...}
$service = $container->get('myservice')
$service has Iimplementations
Thanks in advance
The AbstractPluginManager is mostly for validation and filter plugins. You can create classes and while validating, you can pass specific configuration which makes the filter or validator re-usable.
What you are looking for is probably an abstract factory. You register the factory once and it can create a service for you. In your case with a specific set of dependencies.
interface I{}
class A implements I{}
class B implements I{}
class MyAbstractFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
return in_array('I', class_implements($requestedName), true);
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new $requestedName(
$container->get(DependencyFoo::class),
$container->get(DependencyBar::class)
);
}
}
// config/autoload/dependencies.global.php
return [
'dependencies' => [
'factories' => [
// ...
],
'abstract_factories' => [
MyAbstractFactory::class,
],
],
];
You can also go crazy and use reflection to detect dependencies if they are different for each class, however that adds a lot of overhead. I think it's easier and more maintainable to create separate factories. And then there is zend-expressive-tooling which is a cli tool that can create factories, handlers and middleware.
/*Getting I concrete implementations via the plugin manager will ensure the implementation of the I interface*/
class IPluginManager extends AbstractPluginManager
{
protected $instanceOf = I::class;
public function getIConcreteImplementations()
{
$concreteImpl = [];
foreach(array_keys($this->factories) as $key)
{
$concreteImpl[] = $this->get($key);
}
return $concreteImpl;
}
}
/*IPluginManagerFactory*/
class TransactionSourcePluginManagerFactory
{
const CONFIG_KEY = 'i-implementations-config-key';
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$pluginManager = new IPluginManager($container, $options ?: []);
// If this is in a zend-mvc application, the ServiceListener will inject
// merged configuration during bootstrap.
if ($container->has('ServiceListener')) {
return $pluginManager;
}
// If we do not have a config service, nothing more to do
if (! $container->has('config')) {
return $pluginManager;
}
$config = $container->get('config');
// If we do not have validators configuration, nothing more to do
if (! isset($config[self::CONFIG_KEY]) || !
is_array($config[self::CONFIG_KEY])) {
return $pluginManager;
}
// Wire service configuration for validators
(new Config($config[self::CONFIG_KEY]))->configureServiceManager($pluginManager);
return $pluginManager;
}
}
/*In the ConfigProvider of the module or global config*/
class ConfigProvider
{
/**
* Returns the configuration array
*
* To add a bit of a structure, each section is defined in a separate
* method which returns an array with its configuration.
*
*/
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
'routes' => $this->getRoutes(),
'i-implementations-config-key' => $this->getIConcreteImplementations(),
];
}
public function getIConcreteImplementations() : array
{
return [
'factories' => [
A::class => AFactory::class,
B::class => InvokableFactory::class,
],
];
}
}
/*I can now be sure that I am injecting an array of I implementations into my Service*/
class ServiceFactory
{
public function __invoke(ContainerInterface $container) : Service
{
$pluginManager = $container->get(IPluginManager::class);
$impl = $pluginManager->getIConcreteImplementations();
return new Service($impl);
}
}
I'm trying to print all routes from my modules on "some page" with var_dump() or whatever debug function.
I have found lots of posts and samples but I can't get them printed and most examples fail in my code.
So far I think this is the best way to do so but where to use this code ?
// $sl instanceof Zend\ServiceManager\ServiceManager
$config = $sl->get('Config');
$routes = $config['router']['routes'];
If you want to view all routes just for debugging purposes, you can use var_dump or similar on the router object:
// $sl instanceof Zend\ServiceManager\ServiceManager
$router = $sl->get('Router');
var_dump($router);
You may print all routes from in your controller's method. Look at the following example
module/Application/src/Application/Controller/IndexController.php
<?php
namespace Application\Controller;
use Zend\View\Model\ViewModel;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
/**
* #var array
*/
protected $routes;
/**
* #param array $routes
*/
public function __construct(array $routes)
{
// Here is the catch
$this->routes = $routes;
}
public function indexAction()
{
// Thus you may print all routes
$routes = $this->routes;
echo '<pre>';
print_r($routes);
echo '</pre>';
exit;
return new ViewModel();
}
}
As we passed an array of routes to the constructor of IndexController. We need to make an factory of this controller. A factory is a class that creates instances of other classes.
module/Application/src/Application/Controller/IndexControllerFactory.php
<?php
namespace Application\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$config = $serviceManager->get('Config');
$routes = $config['router'];
return new IndexController($routes);
}
}
A invokable class can not be constructed with arguments. Our controller would not work as invokables because we know we already passed an argument to its constructor. So we need to configure that in factories key under controllers key of our module.config.php
module/Application/config/module.config.php
'controllers' => [
'invokables' => [
// This would not work any more as we created a factory of it
// 'Application\Controller\Index' => 'Application\Controller\IndexController',
],
// We should do it thus
'factories' => [
'Application\Controller\Index' => 'Application\Controller\IndexControllerFactory',
],
],
This answer has been edited for good practice as #av3 suggested!
I'm trying to implement Factory for my Controller:
class NumberControllerFactory implements FactoryInterface{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new NumberController($container->get(Bar::class));
}
public function createService(ServiceLocatorInterface $services)
{
return $this($services, NumberController::class);
}
}
I got error:
Fatal error: Declaration of Number\Factory\NumberControllerFactory::__invoke() must be compatible with Zend\ServiceManager\Factory\FactoryInterface::__invoke(Interop\Container\ContainerInterface $container, $requestedName, array $options = NULL) in C:\xampp\htdocs\MyProject\module\Number\src\Number\Factory\NumberControllerFactory.php on line 10
I need this, because I want to inject model to controller, because service manager has been removed from controllers in Zend 3.
I used skeleton described in https://framework.zend.com/manual/2.4/en/ref/installation.html
In composer.json is:
"require": {
"php": "^5.6 || ^7.0",
"zendframework/zend-component-installer": "^1.0 || ^0.3 || ^1.0.0-dev#dev",
"zendframework/zend-mvc": "^3.0.1",
"zfcampus/zf-development-mode": "^3.0"
},
I don't understand this problem, I read a lot of tutorials, for example:
https://zendframework.github.io/zend-servicemanager/migration/
coould You help me, please?
I guess that currently this method is compatible with Zend\ServiceManager\Factory\FactoryInterface::__invoke
For injecting model into the controller, you need to create a factory class while configuration in module.config.php as below
'controllers' => [
'factories' => [
Controller\AlbumController::class => Factory\AlbumControllerFactory::class,
],
],
Here AlbumController is the controller class of Album module. After that you need to create a AlbumControllerFactory class inside the module\Album\src\Factory.
In this class you need to write the code below:
namespace Album\Factory;
use Album\Controller\AlbumController;
use Album\Model\AlbumTable;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class AlbumControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new AlbumController($container->get(AlbumTable::class));
}
}
You need to write the below code inside the controller class(AlbumController).
public function __construct(AlbumTable $album) {
$this->table = $album;
}
This way you can inject the model class into the controller class.
Thanks Azhar.
My problem was because when I used:
'factories' => array(
\Number\Controller\NumberController::class => \Number\Factory\NumberControllerFactory::class
)
it wasn't work, there was 404... I had to use:
'Number\Controller\Number' => \Number\Factory\NumberControllerFactory::class
in documentation is that I should use full class name ::class.
Does somebody know why it doesn't work?
I wanna use my entities inside my custom plugin. So, I am doing in that order:
1) Declared my plugin in Module\src\Plugin\Plugin.php
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Doctrine\ORM\EntityManager;
use User\Entity\UserProfile;
use Zend\ServiceManager\ServiceManager;
class AuthenticationPlugin extends AbstractPlugin {
protected $entityManager;
protected $serviceManager;
public function setServiceManager(ServiceManager $locator) {
$this->serviceManager = $locator;
}
public function getServiceManager() {
return $this->serviceManager;
}
public function getEntityManager() {
$userEntityFactory = new \Application\Factory\UserEntityFactory();
$this->entityManager = $userEntityFactory->createService($this->getServiceManager());
return $this->entityManager;
}
public function someAction($user_email) {
$user = $this->getEntityManager()->getRepository('User\Entity\User')->findBy(array('email'=>$user_email));
}
}
2) Created my factory:
namespace User\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserEntityFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return $serviceLocator->get('doctrine.entitymanager.orm_default');
}
}
3) Defines it in module.config.php:
'service_manager' => array(
'factories' => array(
'UserEntityFactory' => 'Application\Factory\UserEntityFactory',
),
),
'controller_plugins' => array(
'invokables' => array(
'AuthenticationPlugin' => 'Application\Plugin\AuthenticationPlugin',
)
),
4) Sending ServiceLocator to my plugin in Module.php:
public function getServiceConfig() {
return array(
'factories' => array(
'AuthenticationPlugin' => function($sm) {
$locator = $sm->getServiceLocator();
$instance = new \Application\Plugin\AuthenticationPlugin();
$instance->setServiceManager($locator);
return $instance;
},
),
);
}
5) ...and calling it in onBootstrap:
$em->attach('ZfcUser\Service\User', 'register', function($e) {
$user = $e->getParam('user'); // User account object
$authenticationPlugin = new AuthenticationPlugin();
$authenticationPlugin->someAction($user->getEmail());
});
But I received the error that $locator in plugin is null... I'm confused and I am sure that I'm doing something wrong... or all. I would be happy if somebody will share experiences or will show the order of actions. Thanks.
You don't need to inject the entire service manager object into your plugin class.
You only need to inject the User\Entity\User repository object, this appears to be the only dependancy required in your plugin class.
You should pass this into the constructor of your plugin class via your factory :
public function getServiceConfig() {
return array(
'factories' => array(
'AuthenticationPlugin' => function($sm) {
return new \Application\Plugin\AuthenticationPlugin($sm->get('doctrine.entitymanager.orm_default')->getRepository('User\Entity\User'));
},
),
);
}
in your plugin class:
class AuthenticationPlugin extends AbstractPlugin {
private $userRepository;
public function __construct(\User\Entity\User $userRepository){
$this->userRepository=$userRepository;
}
public function someAction($user_email) {
$user = $this->userRepository->findBy(array('email'=>$user_email));
}
}
As you are configuring the plugin via the module.php you don't need to also declare the plugin as an invokable in your config file. So remove the following line from your module.config.php
'AuthenticationPlugin' => 'Application\Plugin\AuthenticationPlugin'
As a side note, there are various pros and cons between declaring your services/plugins in either the module.php or the module.config file. This though wasn't the question so I won't go into detail here.
I am using Zend Framework 2, and I have created a new input filter that will be used throughout my application. I know that I need to add the new filter to the FilterPluginManager's list so that I can call it when I create inputs. This is the code that I believe I need to use:
\Zend\Filter\FilterPluginManager::setInvokableClass('myFilter', 'Namespace\Filters\MyFilter');
However, I can't find where this should go. Should this be in the bootstrap, or one of the config files, or somewhere else?
this is the answer you want
https://packages.zendframework.com/docs/latest/manual/en/modules/zend.service-manager.quick-start.html
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'UserInputFiler' => 'SomeModule\InputFilter\User',
),
);
}
}
In your Module, implement InputFilterProviderInterface and provide a Factory for the InputFilterPluginManager (AbstractPluginManager):
namespace YourModule;
use Zend\ModuleManager\Feature\InputFilterProviderInterface;
class Module implements InputFilterProviderInterface
{
public function getInputFilterConfig()
{
return [
'factories'=>[
'your_input_filter_name'=>\YourModule\Factories\InputFilterFactory::class
]
];
}
}
Now implement the factory:
namespace YourModule\Factories;
use Zend\Filter\StringTrim;
use Zend\InputFilter\Input;
use Zend\InputFilter\InputFilter;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Validator\StringLength;
class InputFilterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$filter = new InputFilter();
$nameFilter = new Input("name");
$nameFilter->getValidatorChain()->attach(new StringLength([
'min'=>3,
'max'=>16
]));
$nameFilter->getFilterChain()->attach(new StringTrim());
$filter->add($nameFilter);
return $filter;
}
}
Now in your Controller-factory or other factory where you need the InputFilter you can access the InputFilterPluginManager:
$inputFilterManager = $controllerManager->getServiceLocator()->get('InputFilterManager');
return new YourController(
$inputFilterManager->get('your_input_filter_name')
);
Update, you can also add the factory to the module.config.php
'input_filters'=>[
'factories'=>[
'your_input_filter_name'=>InputFilterFactory::class
]
],