ZF2 passing arguments to factories at runtime - zend-framework

In ZF2, I have a factory for a multicheckbox (simplified)
class MultiCheckboxFactory
{
public function __invoke(FormElementManager $formElementManager)
{
$multiCheck = new MultiCheckbox();
$serviceManager = $formElementManager->getServiceLocator();
$mapper = $serviceManager->get('Path\To\The\Mapper\SomeMapper');
$resultFromQuery = $mapper->findText('text');
// further setting up of the multicheckbox based on $resultFromQuery
return $multiCheck;
}
}
I want the multicheckbox to render different content depending on $resultFromQuery that comes from the mapper's findText() method.
I thought of passing a variable to the __invoke(FormElementManager $formElementManager, $someText). But the problem is that when I call the multicheckbox from the service manager like this:
$element = $formElementManager->get('Path\To\Factory\Alias\Multicheckbox');
I don't see how to pass an additional variable. Any help?

Have a look at MutableCreationOptionsInterface, this allows your factory to receive runtime options which you pass through the serviceManager get() method.
use Zend\ServiceManager\MutableCreationOptionsInterface;
use Zend\ServiceManager\MutableCreationOptionsTrait;
class MultiCheckboxFactory implements MutableCreationOptionsInterface
{
use MutableCreationOptionsTrait;
public function __invoke(FormElementManager $formElementManager)
{
$options = $this->getCreationOptions();
var_dump($options);
$multiCheck = new MultiCheckbox();
....
}
}
Now you can pass options:
$element = $formElementManager->get('Path\To\Factory\Alias\Multicheckbox', ['foo' => 'bar']);

Update: MutableCreationOptionsTrait is no longer available in ZF3: https://docs.zendframework.com/zend-servicemanager/migration/#miscellaneous-interfaces-traits-and-classes
The simplest way to do this now appears to be
$element = $formElementManager->build('Path\To\Factory\Alias\Multicheckbox', ['foo' => 'bar']);
though this will give you a discrete (not shared) instance every time.

Related

How to Unittest a Factory with dependencies

How can I test my factory with the entity manager? I have an error because I need to make my container return an instance of a class created from doctrine ( I do not even know what there is returned).
How can I create a test that I can make pass?
// factory i want to test
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$googleAppOption = $container->get(GoogleAppOptions::class);
$em = $container->get('doctrine.entity_manager.orm_default');
return new GoogleTokenHandler($googleAppOption, new GoogleTokenClient(), $em);
}
//test function
public function testReturnsTokenHandlerInstance()
{
$googleOptionsFactory = new GoogleOptionsFactory();
$googleOptions = $googleOptionsFactory($this->container->reveal(), null);
$this->container->get(GoogleAppOptions::class)->willReturn($googleOptions);
$googleTokenHandlerFactory = new GoogleTokenHandlerFactory($this->container);
$tokenHandler = $googleTokenHandlerFactory($this->container->reveal(), null);
$this->assertInstanceof(GoogleTokenHandler::class, $tokenHandler);
}
Th fact that this is hard to test is a good sign that there is something smelly about this. In your case it's quite obviously the container being injected and then being used to locate services to work upon. I would recommend rewriting this class to inject both the OptionsFactory (or even better just the options) and the EntityManager as well as the dynamically created GoogleClient in the constructor. What you would arrive at is an invoke that pretty much looks like this:
return new GoogleTokenHandler(
$this->optionsFactory,
$this->tokenClient,
$this->entityManager
);
As you can see you neither use the $requestedName nor the optional $options being passed to your __invoke. That's a bit odd, but that won't bother us with the tests. Now you can simply mock out the services in your test and check whether invoke returns the correct instance:
public function testFactoryInvokeReturnsInstance()
{
$optionsFactory = $this->prophesize(OptionsFactory::class);
$tokenClient = $this->prophesize(GoogleTokenClient::class);
$entityManager = $this->prophesize(EntityManager::class);
$factory = new MyFactory(
$optionsFactory->reveal(),
$tokenClient->reveal(),
$entityManager->reveal()
);
$this->assertInstanceOf(GoogleTokenHandler::class, $factory->__invoke());
// Alternatively you can use the __invoke-magic directly:
$this->assertInstanceOf(GoogleTokenHandler::class, $factory());
}
You could do the same with your class but basically you would have to add a Container and then stub out the get-method for all of the services being fetched from it. For example you are missing the entity manager in your snippet. Should the GoogleTokenClient being created in your method require some arguments/options there is no way to mock that behavior and in fact you won't be able to switch it out without changing the code. Whereas by injecting it in the constructor you can just re-configure your container to pass in a different object.
For posterity, your complete factory would probably look something like this:
class Factory {
private $optionsFactory;
private $tokenClient;
private $entityManager;
public function __construct(GoogleTokenClient $tokenClient, ...)
{
$this->tokenClient = $tokenClient;
...
}
public function __invoke() { return new GoogleTokenHandler(...); }
}

I have array of plain PHP object that I want to use in conjunction with Zend_Paginator and partialLoop

I am implementing DAO pattern in my sample app and I have plain array that contains User(domain) fetched from UserMapper I want to use Zend_Paginator with array adapter, but it does not work it only works when I use Zend_DbTable adapter which I dont want to do because it defeats the purpose of DAO.
sample code below (Not Working)
$userMapper = new Application_Model_UserMapper();
$users = $userMapper->getUsers();
$paginator = Zend_Paginator::factory($users);
$paginator->setCurrentPageNumber($this->_getParam('page'));
$paginator->setItemCountPerPage(1);
$this->view->paginator = $paginator;
sample code below (Working)
$users = new Application_Model_DbTable_User();
$select = $users->fetchAll();
$paginator = Zend_Paginator::factory($select);
$paginator->setCurrentPageNumber($this->_getParam('page'));
$paginator->setItemCountPerPage(1);
$this->view->paginator = $paginator;
I was looking at the factory method and it takes 3 parameters
public static function factory($data, $adapter = self::INTERNAL_ADAPTER,
array $prefixPaths = null)
you may want to try
$paginator = Zend_Paginator::factory($users, 'Array');
at least this way if your data is somehow incorrect you should raise an exception.
I already solve my problem, in order for array containing plain PHP objects to be recognized by the partialLoop you need to implement a toArray() method in that class and return key value pair of the attributes
class Application_Model_User
{
private $id;
private $first_name;
private $last_name;
private $middle_name;
public function toArray()
{
return get_object_vars($this);
}
}

zend - make a variable global from a routed URL

I'm still experimenting with URL routes and just managed to get it to work.
my routes.ini has this:
[production]
routes.register.route = :lang/register
routes.register.defaults.controller = register
routes.register.defaults.action = index
routes.register.defaults.lang = en
routes.register.reqs.lang = "[a-z]{2}"
My URL would look like this:
http://www.mysite.com/en/register
So now, in my controller I can do this:
$lang = $request->getParam('lang');
My problem is: I'm trying to get a list of countries in a select element, which depending if the lang element is english or french, will return the countries in said language.
To do so, I would need to pass "lang" to the form.
Then in the form, pass it to the model querying the countries there.
And if there's a change, it makes for a lot of places to change as well.
So back to the question:
Can I just set this variable as global?
I would pass the variable to the form from your controller.
// Controller action
public function formAction()
{
$lang = $this->getParam('lang');
$form = new My_Form_Xyz($lang);
$this->view->form = $form;
}
// My_Form_Xyz
protected $lang;
public function __construct($lang)
{
$this->lang = $lang;
parent::__construct();
}
public function init()
{
switch ($this->lang) {
case 'en':
$selectOptions = array();
break;
case 'klingon':
$selectOptions = array();
break;
}
}
Injecting via form setter (as suggested by ArneRie) is better solution. But in case you don't have a discrete form class, or you generate the form from ini file, you can access the parameter like this anywhere after $front->dispatch() in your bootstrap
Zend_Controller_Front::getInstance()->getRequest()->getParam('lang');

Zend Framework: How to pass variables to a custom form element's view helper

So I've created myself a custom form element which has a custom view helper. Now I want to be able to set certain parameters/variables on this form element and be able to access them in my element's view helper. How can I do that?
Here's an example of what I am talking about:
adding the element to the form:
$element = new My_Form_Element_Picker('elementname');
$element->setFoobar('hello');
// or
$form->addElement('Picker', 'elementname', array('foobar' => 'hello'));
form element:
class My_Form_Element_Picker extends Zend_Form_Element_Xhtml
{
public $helper = 'pickerElement';
}
view helper:
class My_View_Helper_PickerElement extends Zend_View_Helper_FormElement
{
public function pickerElement($name, $value = null, $attribs = null)
{
//now I want to check if the 'foobar' option was set, otherwise use a default value
$foobar = 'default';
}
}
There is a fourth optional argument to the view helper that might do the trick for you.
if you define your view helper like this:
public function pickerElement( $name, $value=null, $attribs=null, $options=null ) { }
And then inside your actual form element you define it like this:
class My_Form_Element_Picker extends Zend_Form_Element_Xhtml {
public $helper = 'pickerElement';
public $options = array();
public function setFoobar( $foobar ) {
$this->options['foobar'] = $foobar;
}
}
You will find that the options are passed into the view helper and can be used.
This code is from memory so please forgive any mistakes, this method definitely works for me though.

zend-framework, call an action helper from within another action helper

i am writing an action helper and i need to call another action helper from within that helper. but i dont know how. here in the sample code:
class Common_Controller_Action_Helper_SAMPLE extends Zend_Controller_Action_Helper_Abstract
{
protected $_view;
public function __construct(Zend_View_Interface $view = null, array $options = array())
{
$this->_view = $view;
}
public function preDispatch()
{
$flashMessenger = $this->_helper->FlashMessenger; // IT IS NULL
}
}
Use the action helper broker:
$flashMessenger =
Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger');
You can also use getActionController to get a reference back to the actioncontroller you were using for any methods you'd normally use there.
In addition to mercator's answer, add your method after, see example below:
Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger')->myMethod();
You can call it in this way:
$this->_actionController->OtherActionHelper();
The _actionController property references the actual action controller.