Zend 3 Factory Interface - php

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?

Related

How to call built-in twig function from custom twig filter?

I want to create a custom filter which will return HTML link to client profile.
I need something like:
<?php
namespace App\Twig;
use App\Entity\Client;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class ClientExtension extends AbstractExtension
{
/**
* {#inheritdoc}
*/
public function getFilters(): array
{
return [
new TwigFilter('client_link', [$this, 'createClientLink'], [
'is_safe' => ['html'],
'needs_environment' => true,
]),
];
}
public function createClientLink(Environment $environment, Client $client): string
{
// this code is an example, such function does not exist
$url = $environment->callTwigFunction('path', 'client_profile', [$client->getId()]);
return ''.$client->getEmail().'';
}
}
What I cannot figure out is how do I call the path twig function? Is there some callTwigFunction-like feature I can use?
I have implemented it this way:
$url = $environment->getExtension(Symfony\Bridge\Twig\Extension\RoutingExtension\RoutingExtension::class)->getPath('cp_client_show_summary', ['id' => $client->getId()]);
Thanks to the suggestion of #Cerad
EDIT:
I am going to keep the above code because it shows the answer to the more general question I had about "calling built-in twig functions from inside filters". However as pointed by others in the comments, the proper way for my case was to inject UrlGeneratorInterface:
class AppExtension extends AbstractExtension
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
}
//...
and then generate URL like this:
$url = $this->urlGenerator->generate('cp_client_show_summary', ['id' => $client->getId()]);

Using AbstractPluginManager with Zend Expressive 3

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

ServiceManager in ZF3

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,
],

ZF2 service locator interface does not return an object

I am trying to create a model that will have access to the ZF2 service locator.
I have a model class that looks like this:
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class testDelete implements ServiceLocatorAwareInterface
{
protected $services;
/**
* construct function
*/
public function __construct ()
{
$router = $this->getServiceLocator()->get('Router');
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
}
In reading the tutorials and blogs this should give me an instance of the service locator, which I can then use to call classes. But when I call it I get a message
Fatal error: Call to a member function get() on a non-object...
Does anyone know why this would be?
Do I need to some sort of setup to get the service locator interface to work?
Because your class implements ServiceLocatorAware, the service manager will automatically inject the service locator into it. However, it can only do that if the service manager is the thing instantiating the testDelete class. So you need to setup a service for testDelete.
Once you've done that, you still won't be able to call $this->getServiceLocator() from __construct(), as the dependency won't have been injected into the class yet.
If all you want is to get the router into your testDelete class, just create a service for testDelete and pass the router in as a dependency. This would be much easier than what you're currently trying to do.
It looks like you need to set the "Router" service in the getServiceConfig() method of your Modules Module.php file:
public function getServiceConfig()
{
return array
(
'factories' => array
(
'Router' => function($serviceManager)
{
... your logic ...
return $Router
},
),
);
}
Your Module.php file will look something like this:
namespace ModuleName;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
/**
* Tables (& Databases) Used in this Module
*/
use Your\Model\ModelName;
use Your\Mapper\ModelMapperName;
class Module implements AutoloaderProviderInterface
{
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$serviceManager = $e->getApplication()->getServiceManager();
$sharedManager = $e->getApplication()->getEventManager()->getSharedManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
/**
* Additional logic for Setting up Logging, Pre-Initializations, Exceptions etc
*/
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array
(
'Zend\Loader\ClassMapAutoloader' => array
(
include __DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array
(
'namespaces' => array
(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getServiceConfig()
{
return array
(
'factories' => array
(
'Router' => function($serviceManager)
{
... your logic ...
return $Router
},
),
);
}
}

How to add a custom input filter to Zend Framework 2?

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
]
],

Categories