Reputation: 261
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).
Upvotes: 0
Views: 469
Reputation: 828
You can declare your viewhelper
in 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.
Upvotes: 0