zf2 view helper factory / service locator parameters - zend-framework2

I have a view helper that acts as a factory by returning an entity-specific renderer.
I would like the factory to implement the FactoryInterface and MutableCreationOptionsInterface, so i can return different renderers depending on the type of object passed to it, eg:
$serviceLocator->get('entityRenderer', ['entity' => $user]); // returns UserRenderer
$serviceLocator->get('entityRenderer', ['entity' => $admin]); // returns AdminRenderer
$serviceLocator->get('entityRenderer'); // returns DefaultRenderer
However, there is no access to the servicelocator from within a view, and the factory view helper i have created is called using it's __invoke method. This means the type check is occuring here and returning the specific renderer without using the service manager, which is not desirable. eg
class EntityRendererFactory extends AbstractHelper{
public function __invoke(Entity $entity){
if($entity instanceof User){
$renderer = new UserRenderer($entity);
$renderer->setView($this->view);
}
if($entity instanceof Admin){
$renderer = new AdminRenderer($entity);
$renderer->setView($this->view);
}
if($renderer){
return $renderer;
}
}
}
Note how this "factory" is having to extend AbstractHelper (view) simply just to pass on the instance of the current view.
My "ideal" would be something like this (proof of concept, not working code):
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
class EntityRendererFactory implements FactoryInterface, MutableCreationOptionsInterface{
protected $options = [];
protected $renderers = [];
public function createService(ServiceLocatorInterface $serviceLocator){
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('UserRenderer'), User::class);
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('AdminRenderer'), Admin::class);
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('DefaultRenderer'), 'default');
if(!array_key_exists('entity', $this->options)){
return $this->getRenderer('default');
}
$entity = $this->options['entity'];
foreach($this->getRenderers() as $renderer){
if($renderer->canRender($entity)){
return $renderer;
}
}
//Alternatively, more specific hard-coding interface type check
if($entity instanceof User){
return $serviceLocator->get('ViewHelperManager')->get('UserRenderer');
}
//etc.
}
public function setCreationOptions(array $options){
$this->options = $options;
}
}
...but with the above demonstration, i would be unsure how to call it from within the view (as view helpers are typically called from their __invoke method and not from the service manager)?
(With an eye to migrating to ZF3, i do not want to use the ServiceLocatorAwareInterface).

You can declare your viewhelperin the factories section of module.config.php as :
return [
...
'view_helpers' => [
'factories' => [
'entityRenderer' => EntityRendererFactory::class
]
],
...
]
then use the following model :
class EntityRendererFactory extends AbstractHelper implement FactoryInterface
{
private $sm;
public function createService(ServiceLocatorInterface $serviceLocator){
$this->sm = $serviceLocator;
return $this;
}
public function _invoke() {
// your code
}
}
Personally, I start by creating a specific service manager containing only the necessary classes and it is this one that I record in the class.
$this->sm = $servicelocator->getServiceLocator()->get('mySpecificSM');
Incidentally, this model does not work under ZF3 where you need a factory class that builds the viewhelper class. A change not too complicated however.

Related

How to inject Service to have a common instance to several clasess them in zend 3

In Zend 3, I cannot to figure out how should injections work, let me show some simple example.
Let's say, I have
class ToolCollector implements ToolCollectorInterface {
public function __construct(){}
public function registerTool(ToolInterface $tool){
$this->tools[] = $tool;
}
}
and also
class ToolA implements ToolInterface {
public function __construct(ToolCollectorInterface $mainCollector){$mainCollector->registerTool($this);}
}
so what I expect to have
class SomeController extends AbstractActionController {
public function __construct(ToolCollectorInterface $toolCollector){$this->collector=$toolCollector;}
public function indexAction(){
new ToolA; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolB; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolC; // <- just call Tool and it will put itself in ToolCollector, this is I want
return ViewModel(['toolNameList'=>$this->collector->getRegisteredToolNames();])
}
}
I tried to make it as shown in example, when i call new ToolA it shows me error 500 without any description.
// Next is works
class ToolControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
// Next is not reachable, And I do not know why...
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
In module.config.php
return [
...
'service_manager' => [
'aliases' => [
ToolCollectorInterface::class => ToolCollector::class,
],
'factories' => [
Service\ToolCollector::class => InvokableFactory::class,
],
],
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
...
];
Is it possible to do it?
How to write factory to make it possilbe?
You must understand something very easy to spot using IoC Inversion of Control. The Container is handling the building process and a reference to the instances you need.
So it is cumbersome and useless, in fact, to make a new inside your indexAction().
You should (and, in fact, you must do it to follow best practices) make any new inside your factory for each dependency your SomeController has.
So you will have many factories. For your Controllers, for your Services, for your Repositories, etc. Each factory has its own logic to build the object you need.
If there is no deps, you can use the default InvokableFactory factory without to write yours. If there is at least one dep, then you must write your own factory with returning your Controller / Service / Repository / etc. class which is built as a ready to use object instance.
The code :
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
I do not see any ToolFactory inside your config, only a ToolControllerFactory :
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
So it is normal that is sending you a http 500 error, because it does not know the factory your are trying to use to build your Controller.
Zend Framework / Laminas is very tied to configuration. You must follow principles and then you can build your own path to make the things work. But, much important thing, you must be structured enough to not lose yourself in the forest.
Go step by step without the will to change the world when you are starting to walk. Dreams are necessary. But you have to handle a learning curve firstly, as any of us !

Circular dependency for LazyServiceLoader was found for instance `UserService`

In Zend Framework 2,
I have a controller class UserController
UserController is dependent on UserService
UserService is dependent on UserChangedListener
UserChangedListener is dependent on SomeOtherClass
SomeOtherClass is dependent on UserService
So here my UserController and SomeOtherClass are dependent on UserService.
I am getting error :
Circular dependency for LazyServiceLoader was found for instance
UserService
The above error (i.e. Circular dependency for LazyServiceLoader) occurred when I injected SomeOtherClass in UserChangedListener
And I have
"zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3",
UserControllerFactory.php
class UserControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$container = $container->getServiceLocator();
return new UserController(
$container->get(UserService::class)
);
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
return $this($serviceLocator, UserController::class);
}
}
UserServiceFactory.php
class UserServiceFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$service = new UserService(
$container->get(UserRepository::class)
);
$eventManager = $service->getEventManager();
$eventManager->attach($container->get(UserChangedListener::class));
return $service;
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
return $this($serviceLocator, UserService::class);
}
}
UserChangedListenerFactory.php
class UserChangedListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$container = $container->getServiceLocator();
return new UserChangedListener(
$container->get(SomeOtherClass::class)
);
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
return $this($serviceLocator, UserChangedListener::class);
}
}
SomeOtherClassFactory.php
class SomeOtherClassFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$rootLocator = $serviceLocator->getServiceLocator();
return new SomeOtherClass(
$rootLocator->get(UserService::class)
);
}
}
Looks like you have a legitimate circular dependency with UserService. The error message is telling you that the UserService cannot be created without the UserService.
This is really a problem introduced by the good practice of using dependency injection via the __construct method. By doing so, ZF2 will eager load a very large object graph into memory, when you have lots of related 'services' that have complex nested relationships you are bound to have circular dependencies.
ZF2 does offer Lazy Services as a solution to delay the instantiation of certain objects that as the developer you will need to decide on which ones (I would suggest UserChangedListener).
Alternatively to update your code, you could move the registration of the listener code outside of the UserServiceFactory and into the Module::onBootstrap() method.
namespace User;
class Module
{
public function onBootstrap(EventInterface $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// Create the user service first
$userService = $serviceManager->get(UserService::class);
$listener = $serviceManager->get(UserChangedListener::class);
// The create and attach the listener after user service has been created.
$userService->getEventManager()->attach($listener);
}
}
This would be required if you are using the 'aggregate' listeners. For simple event listeners you can also use the SharedEventManager which would prevent the overhead of loading the UserService in the above example.
UserService is dependent on SomeOtherClass through
UserChangedListener
SomeOtherClass is dependent on UserService
So basically for creation UserService you need first create SomeOtherClass instance, but for create it you need UserService instance to be created already.
I'm not sure about your architecture, but according to class names it looks bit wrong that you attach UserChangedListener in UserService.
Probably UserService should only fire events, and shouldn't know anything about listeners for this events. But again - that just idea, and for good answer you need to explain this dependencies bit more.

How can I set up Lazy Loading with ZF3 (no ServiceLocator pattern from anywhere)

I am writing a new ZF2 app. I have noticed that ServiceLocator usage pattern of calling services "from anywhere" has been deprecated from ZF3. I want to write code in mind for ZF3.
I was able to set up my Controller to call all dependencies at constructor time. But that means loading i.e. Doctrine object upfront before I need it.
Question
How do I set it up so that it is only loaded when I need it immediately? (lazy-loaded). I understand that ZF3 moves loading to Controller construction, which makes it not apparent as to how to load something Just-In-Time.
Old Code
class CommissionRepository
{
protected $em;
function getRepository()
{
//Initialize Doctrine ONLY when getRepository is called
//it is not always called, and Doctrine is not always set up
if (! $this->em)
$this->em = $this->serviceLocator->get('doctrine');
return $this->em;
}
}
Current Code after Refactor of ServiceLocator pattern
class CommissionRepository
{
protected $em;
function getRepository()
{
return $this->em;
}
function setRepository($em)
{
$this->em = $em;
}
function useRepository($id)
{
return $this->em->find($id);
}
}
class CommissionControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $controllerManager->getServiceLocator();
// set up repository
$repository = new CommissionRepository();
$repository->setRepository($parentLocator->get('doctrine'));
// set up controller
$controller = new CommissionController($repository);
$controller->setRepository();
return $controller;
}
}
class CommissionController extends AbstractActionController
{
protected $repository;
public function setRepository(CommissionRepository $repository)
{
$this->repository = $repository;
}
public function indexAction()
{
//$this->repository already contains Doctrine but it should not
//I want it to be initialized upon use. How?
//Recall that it has been set up during Repository construction time
//and I cannot call it from "anywhere" any more in ZF3
//is there a lazy loading solution to this?
$this->repository->useRepository();
}
If you don't have any valid/strong reason to instantiate a custom entity repository, you should prefer extending of Doctrine\ORM\EntityRepository in your repositories like CommissionRepository. For example;
use Doctrine\ORM\EntityRepository;
class CommissionRepository extends EntityRepository
{
// No need to think about $em here. It will be automatically
// injected by doctrine when you call getRepository().
//
function fetchCommissionById($id)
{
// You can easily get the object manager directly (_em) or
// using getEntityManager() accessor method in a repository
return $this->_em->find($id);
}
}
By this way, entity manager will be automatically injected to the repository on construction when you call the $em->getRepository('App\Entity\Commission') method.
I assume that you already have a Commission entity in your app's Entity namespace:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repo\CommissionRepository")
* #ORM\Table
*/
class Commission
{
}
Then you can simplify the injecting process of the repository in your factory something like:
// ZF2 Way
class CommissionControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $services)
{
$em = $services->getServiceLocator()->get('doctrine');
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
}
UPDATE - With the release of Service Manager V3, FactoryInterface has been moved to Zend\ServiceManager\Factory namespace (1), factories are literally invokables (2) and works with any container-interop compatible DIC (3) Updated factory would be like below:
// ZF3 Way
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
use Doctrine\ORM\EntityManager;
class CommissionControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $dic, $name, array $options = null) {
$em = $dic->get(EntityManager::class);
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
}
For the question; as of marcosh's said, Lazy Services are way to go to create services when need it immediately. ZF3 will use the zend-servicemanager 3.0 component when released. (Currently zend-expressive uses it) As of servicemanager v3 you can create some proxied services by defining lazy_services and delegators in your service configuration:
'factories' => [],
'invokables' => [],
'delegators' => [
FooService::class => [
FooServiceDelegatorFactory::class,
],
],
'lazy_services' => [
// map of service names and their relative class names - this
// is required since the service manager cannot know the
// class name of defined services up front
'class_map' => [
// 'foo' => 'MyApplication\Foo',
],
// directory where proxy classes will be written - default to system_get_tmp_dir()
'proxies_target_dir' => null,
// namespace of the generated proxies, default to "ProxyManagerGeneratedProxy"
'proxies_namespace' => null,
// whether the generated proxy classes should be written to disk or generated on-the-fly
'write_proxy_files' => false,
];
Also, starting with service manager v3 factories are compatible with the ContainerInterface. For the forward-compatibility, you may want to keep both __invoke() and createService() methods in your factories for a smooth migration.
In the end, your ZF3 compatible factory may look like:
class CommissionControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$em = $container->get('doctrine');
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
public function createService(ServiceLocatorInterface $container, $name = null, $requestedName = null)
{
return $this($container, $requestedName, []);
}
}
Hope it helps.

ZendFramework 2 - how to call zfcUser from the Model class

I am trying to call ZFCUser from a class within my model but i keep getting this message:
Call to undefined method Members\Model\MemberTable::zfcUserAuthentication()
this is how i tried to call it:
public function getUserEntity()
{
if($this->zfcUserAuthentication()->getAuthService()->hasIdentity())
{
if (!$this->user_entity)
{
$this->setUserEntity($this->zfcUserAuthentication()->getAuthService()->getIdentity());
}
return $this->user_entity;
}
}
i suspect that i need to implement/extend a class inorder for ZFcuser to be recoginised.
i would really appriciate some quick advice on this.
thank you
by the way
the ZFCUser works when i call it from my controller so, the use Zend\Mvc\Controller\AbstractActionController obviouly enables this to be recoginised.
but what is the equivalent of the AbstractActionController for other classes in your framework.
zfcUserAuthentication() is a controller plugin, so that's why it can be called from controllers. You can't (or shouldn't) try and access this from non-controllers. If you need the user entity within another class, you should pass it in as a dependency for that class.
Edit: Update your member table factory so it sets the user entity:
'Members\Model\MemberTable' => function($sm) {
$tableGateway = $sm->get('MemberTableGateway');
$table = new MemberTable($tableGateway);
$authService = $serviceLocator->get('zfcuser_auth_service');
$userEntity = $authService->getIdentity();
$table->setUserEntity($userEntity);
return $table;
}
add a property to your MemberTable class for it:
protected $userEntity;
and getters/setter for it:
public function setUserEntity($userEntity)
{
$this->userEntity = $userEntity;
}
public function getUserEntity()
{
return $this->userEntity;
}
then just call $this->getUserEntity() when you need it.
There are various ways to do that -
One of the way is - do the following in "MemberTable.php" file.
Maybe most of the below lines of code is already available in our project.
a. Add the below lines after 'namespace' statement -
use Zend\ServiceManager\ServiceLocatorAwareInterface; //Added Line
use Zend\ServiceManager\ServiceLocatorInterface; //Added Line
b. Change the class statement as -
class MemberTable implements ServiceLocatorAwareInterface { //Modified Line
....
.....
}
c. Add the below line at the top of the class statement -
class MemberTable implements ServiceLocatorAwareInterface {
protected $serviceLocator; //Added line
....
.....
}
d. Add the following functions -
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
e. In Members/Module.php, make the changes,
'Members\Model\MemberTable' => function($sm) {
$tableGateway = $sm->get('MemberTableGateway');
$table = new MemberTable($tableGateway);
$table->setServiceLocator($sm); //This is the important line.
return $table;
}
Now you have the 'ServiceLocator' available in the MemberTable class.
For accessing the zfcUserAuthentication() or any ControllerPlugin, you can do the following -
public function getUserEntity()
{
$zfcUserAuth = $this->getServiceLocator()->get('controllerPluginManager')->get('zfcUserAuthentication');
if($zfcUserAuth->getAuthService()->hasIdentity())
{
if (!$this->user_entity)
{
$this->setUserEntity($zfcUserAuth->getAuthService()->getIdentity());
}
return $this->user_entity;
}
}

Best way to use ServiceManager in Model Class?

I'm trying to use Service Manager on my entity class but I don't know the best way to do that.
It's easy on a controller because we can call service manager with : $this->getServiceLocator();
But, in my entity class, even if I implements ServiceLocatorAwareInterface i can retieve ServiceManager because my entity class isn't call with the service manager :
So what is the best way :
1 - Pass serviceManager in my entity class from my controller
2 - Using ServiceManager to build my entity class
3 - ... ?
To best understand my problem, that's my code which doesn't work :
My entity class:
class Demande extends ArraySerializable implements InputFilterAwareInterface {
/../
public function getUserTable() {
if (! $this->userTable) {
$sm = $this->getServiceLocator();//<== doesn't work !
$this->userTable = $sm->get ( 'Application\Model\UserTable' );
}
return $this->userTable;
}
I wouldn't inject the ServiceManager into your model (although you can). I would rather get the ServiceManager to build your Model for you, and inject anything you need directly into the model.
Service Config:
'factories' => array(
'SomethingHere' => function($sm) {
$model= new \My\Model\Something();
return $model;
},
'\My\Model\Demande' => function($sm) {
$model= new \My\Model\Demande();
/**
* Here you use the SM to inject any dependencies you need
* into your model / service what ever..
*/
$model->setSomething($sm->get('SomethingHere'));
return $model;
},
/**
* Alternatively you can provide a class implementing
* Zend\ServiceManager\FactoryInterface
* which will provide an instance for you instad of using closures
*/
'\My\Model\DemandeDefault' => '\My\Model\DemandeFactory',
Place any of your dependencies inside the Service Manager Config, and then use that to inject any dependencies into your models, services etc for you.
An example factory class if you want to use the factory method rather than closures:
DemandeFactory.php
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class DemandeFactory implements FactoryInterface
{
/**
* Create a new Instance
*
* #param ServiceLocatorInterface $serviceLocator
* #return Demande
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config'); // if you need the config..
// inject dependencies via contrustor
$model = new \My\Model\Demande($serviceLocator->get('SomethingHere'));
// or using setter if you wish.
//$model->setSomething($serviceLocator->get('SomethingHere'));
return $model;
}
}
An example Model you are trying to instantiate via the Service Manager.
Demande.php
class Demande
{
protected $_something;
/**
* You can optionally inject your dependancies via your constructor
*/
public function __construct($something)
{
$this->setSomething($something);
}
/**
* Inject your dependencies via Setters
*/
public function setSomething($something)
{
$this->_something = $something;
}
// Something will be injected for you by the Service Manager
// so there's no need to inject the SM itself.
}
In your Controller:
public function getDemande()
{
if (! $this->_demande) {
$sm = $this->getServiceLocator();
$this->_demande = $sm->get ('\My\Model\Demande');
}
return $this->_demande;
}
You could inject the SergiceManager/ServiceLocator into your models but then your models will depend on the ServiceLocator.

Resources