Injecting variables into Forms __construct function Zend 2 - forms

I am trying to prepopulate a form's drop down options via data stored statically in the module.config.php in Zend 2 and am running into a problem which entails:
I try to get the Servicemanager in the __construct() function but it is unavailable.
I move the element declarations to another function within the form class so I can pass variables into it but the view controller cannot find the elements.
I currently call the form via a Servicemanager Invokable. How can I pass these arrays into the form's __construct() function?
Here is the code:
class ILLForm extends Form
{
public function __construct($fieldsetName, $campuses, $ILLTypes, $getFromOptions)
{
parent::__construct('create_ill');
$this
->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter())
;
$ill = new ILLFieldset('ill', $campuses, $ILLTypes, $getFromOptions);
$ill->setName('ill')
->setOptions(array(
'use_as_base_fieldset' => true,
));
$captcha = new Element\Captcha('captcha');
$captchaAdapter = new Captcha\Dumb();
$captchaAdapter->setWordlen(5);
$captcha->setCaptcha($captchaAdapter)
->setLabelAttributes(array('class' => 'sq-form-question-title'))
->setAttribute('class', 'sq-form-field required')
->setLabel("* Captcha")
->setAttribute('title', 'Help to prevent SPAM');
$submit = new Element\Submit('submit');
$submit->setAttribute('value', 'Submit ILL')
->setAttribute('class', 'sq-form-submit')
->setAttribute('title', 'Submit ILL');
$this->add($ill)
->add($captcha)
->add($submit);
}
}
The Indexcontroller Factory that calls the Form:
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$allServices = $controllers->getServiceLocator();
$sm = $allServices->get('ServiceManager');
$smConfig = $allServices->get('config');
$campuses = $smConfig['campuses'];
$illCategories = $smConfig['ILLCategories'];
$getFromOptions = $smConfig['getFromOptions'];
$controller = new IndexController();
$controller->setCampuses($campuses);
$controller->setILLCategories($illCategories);
$controller->setGetFromOptions($getFromOptions);
$controller->setILLForm($sm->get('ILL-form'));
$controller->setILLFormFilter($sm->get('ILL-form-filter'));
//$controller->setParams($sm->get('params'));
return $controller;
}
}
and the relevant module.config.php excerpt:
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'invokables' => array(
'ILL-form-filter' => 'ILL\Form\ILLFormFilter',
'ILL-form' => 'ILL\Form\ILLForm',
),

I ended up taking the form out of the service manager invokables in module.config.php (removed line for 'ILL-form') and call it from the indexControllerFactory.php instead
$create_ill = new Form\ILLForm('create_ill', $campuses, $illCategories, $getFromOptions);
$controller->setILLForm($create_ill);
instead of
$controller->setILLForm($sm->get('ILL-form'));

Related

Scanning translatable strings in zend 3 forms with Poedit

Zend 3 translates form labels automatically.
If forms are created using array specification, how is it possible to scan translatable form element strings with Poedit?
How to add translator->translate() functionality to forms? I tried the following in module.php onBootstrap method but this does not work:
$sm = $e->getApplication()->getServiceManager();
$vhm = $sm->get('ViewHelperManager');
$translator = $sm->get('MvcTranslator');
$vhm->get('form')->setTranslator($translator);
I want to use it like $form->translator->translate(), in such a way it would be possible to scan code with Poedit to find translatable labeles, placeholders etc.
Here's a TranslatorFactory if you need
final class TranslatorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// get translator files' paths from config
$paths = $container->get('config')['settings']['localization-paths'] ?? [];
$translator = new Translator();
// add zend-i18n-resources files to translator
$translator->addTranslationFilePattern(
'phpArray',
Resources::getBasePath(),
Resources::getPatternForValidator()
);
// add your translation files to translator
foreach ($paths as $path) {
$translator->addTranslationFilePattern('phpArray', $path, '%s.php');
}
// todo: replace with user's preferred language
$translator->setLocale('tr');
return $translator;
}
}
And add your factory to service manager
'service_manager' => [
'factories' => [
\Zend\I18n\Translator\TranslatorInterface::class => \MyModule\Factory\TranslatorFactory::class,
],
],
Not sure if you're still looking for a solution, so I'll add mine.
I use the TranslatorAwareTrait in my AbstractForm class.
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
// Form stuff
}
Then, in the *FormFactory do the following:
use Zend\I18n\Translator\Translator;
use Zend\ServiceManager\Factory\FactoryInterface;
class SomeFormFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
* #return mixed|object|AbstractForm
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Obviously you'll have more/other requirements. Fulfil them here.
$form = new SomeForm();
$form->setTranslator(
$container->get('translator')
);
return $form;
}
}
Usage example:
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
public function init()
{
if (!$this->has('submit')) {
$this->addSubmitButton();
}
}
public function addSubmitButton($value = 'Save', array $classes = null)
{
$this->add([
'name' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' =>
// Translate $value before passing to this function
$value === 'Save' ? $this->getTranslator()->translate('Save') : $value,
'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
],
]);
}
}
On the other hand, you could...
Translate strings before passing them if you're translating with Poedit.
If your modules contain the following config (in each module!):
'translator' => [
'translation_file_patterns' => [
[
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
],
],
],
You can see here that translation is done using gettext. This is a PHP module that searches for the following code strings and translates its contents: _('translatable string').
The translation files to look for end with the .mo extension and can be found in __DIR__ . '/../language'.
Thus, if you make sure to have the PHP gettext module enabled to use this.
To use this with just normal strings, even in config for a Fieldset or a form, you could have the following:
$this->add([
'name' => 'street',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Street'), // <<-- translated using gettext
],
]);

unit testing legacy code

I'm new to unit testing and trying to unit test the model validation of an old zend application which is using forms.
Inside one of the forms it creates an instance of a second class and I'm struggling to understand how I can mock the dependent object. The form reads as follows :
class Default_Form_Timesheet extends G10_Form {
public function init() {
parent::init();
$this->addElement( 'hidden', 'idTimesheet', array( 'filters' => array ('StringTrim' ), 'required' => false, 'label' => false ) );
$this->addElement('checkbox', 'storyFilter', array('label' => 'Show my stories'));
$user = new Default_Model_User();
$this->addElement('select', 'idUser', array('filters' => array('StringTrim'), 'class' => 'idUser', 'required' => true, 'label' => 'User'));
$this->idUser->addMultiOption("","");
$this->idUser->addMultiOptions($user->fetchDeveloper());
...
......
My problem occurs when the call is made to $user->fetchDeveloper(). I suspect it has something todo with mocking objects and dependency injection but any guidence would be appreciated. My Failing unit test reads as follows...
require_once TEST_PATH . '/ControllerTestCase.php';
class TimesheetValidationTest extends ControllerTestCase {
public $Timesheet;
public $UserStub;
protected function setUp()
{
$this->Timesheet = new Default_Model_Timesheet();
parent::setUp();
}
/**
* #dataProvider timesheetProvider
*/
public function testTimesheetValid( $timesheet ) {
$UserStub = $this->getMock('Default_Model_User', array('fetchDeveloper'));
$UserStub->expects( $this->any() )
->method('fetchDeveloper')
->will( $this->returnValue(array(1 => 'Mickey Mouse')));
$Timesheet = new Default_Model_Timesheet();
$this->assertEquals(true, $Timesheet->isValid( $timesheet ) );
}
My data provider is in a separate file.
It is terminating at the command line with no output and I'm a bit stumped. Any help would be greatly appreciated.
You can't mock the Default_Model_User class in your test for the form. Because your code is instantiating the class internally you are not able to replace it with a mock.
You have a couple of options for testing this code.
You look into what fetchDeveloper is doing and control what it is returning. Either via a mock object that you can inject somewhere (looks unlikely) or by setting some data so that you know what the data will be. This will make your test a little brittle in that it could break when the data you are using changes.
The other option is to refactor the code so that you can pass the mock into your form. You can set a constructor that would allow you to set the Default_Model_User class and then you would be able to mock it with your test as written.
The constructor would like like this:
class Default_Form_Timesheet extends G10_Form {
protected $user;
public function __construct($options = null, Default_Model_User $user = null){
if(is_null($user)) {
$user = new Default_Model_User();
}
$this->user = $user;
parent::__construct($options);
}
Zend Framework allows options to be passed to forms constructor which I am not sure if you use in your code anywhere so this should not break any of your current functionality. When can then pass an optional Default_Model_User again so as to not break your current functionality. You need to set the values for $this->user before calling parent::__construct otherwise Zend will throw an error.
Now your init function will have to change from:
$user = new Default_Model_User();
to
$user = $this->user;
In your test you can now pass in your mock object and it will be used.
public function testTimesheetValid( $timesheet ) {
$UserStub = $this->getMock('Default_Model_User', array('fetchDeveloper'));
$UserStub->expects( $this->any() )
->method('fetchDeveloper')
->will( $this->returnValue(array(1 => 'Mickey Mouse')));
$Timesheet = new Default_Model_Timesheet(null, $UserStub);
$this->assertEquals(true, $Timesheet->isValid( $timesheet ) );
}
Creating a mock doesn't replace the object so that when new is called that your mock object is created. It creates a new object that extends your class that you can now pass around. new is a death to testability.

Zend_Form renders all fields as text

I have a Zend_Form form, with some custom decorators, like this:
$decorators = array();
$decorators[] = new Zend_Form_Decorator_ViewHelper(array());
$decorators[] = new Zend_Form_Decorator_Errors;
$decorators[] = new Zend_Form_Decorator_HtmlTag(array('tag' => 'div', 'class' => 'form-item'));
$decorators[] = new Zend_Form_Decorator_Label(array('class' => 'form-label'));
$decorators[] = new Zend_Form_Decorator_Callback(array(
'callback' => function($content, $element, $options) {
return sprintf('<div class="form-row">%s</div>', $content);
},
'placement' => false
));
$this->setElementDecorators($decorators);
The problem is, that all of the fields are rendered as text inputs. Why does it happen?
EDIT: I discovered, that it doesn't render all the inputs necessarily as text inputs, but renders them with type of the first input in form. Here is example of a form that i use(the decorators are set int parent's init):
<?php
class Form_Users_Add extends Form_Base {
protected $pbxs = array(1 => 'Element 1', 2 => 'Element 2');
public function init() {
$monitors = new Zend_Form_Element_Checkbox('prefered_screen_count');
$monitors->setCheckedValue(2);
$monitors->setUncheckedValue(1);
$monitors->setLabel('two_monitors');
$this->addElement($monitors);
$pbx = new Zend_Form_Element_Select('asterisk_id');
$pbx->setMultiOptions($this->pbxs);
$pbx->setLabel('users_asterisk_id');
$this->addElement($pbx);
parent::init();
}
}
Yay! I have solved the issue! The cause was that I used INSTANCES of classes, not the names. This way every element was using the same instance of the decorator.

sfWidgetFormInputText weird behaviour

Well, I have this form:
class CaracteristicaForm extends sfForm {
public function configure() {
$this->setWidgets(array(
'caracteristica' => new sfWidgetFormInputText(array('default'=>'hola mundo'))
));
$this->setValidators(array(
'caracteristica' => new sfValidatorString(
array(
'max_length' => 150,
'required' => true
)
)
));
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
}
}
and then I try to...
$form = new CaracteristicaForm();
$this->embedForm('caracteristica', $form);
but the rendered inputText does not have any value at all.
What am I missing?
Ok, to reproduce this you need to embed this form into another form (mine is a Doctrine child form) so maybe is something about some method changing the value?
Well, passing an array defaults did the trick:
$form = new CaracteristicaForm(array('caracteristica' => $caracteristica));
Why not try to
$form->setDefault('caracteristica', $caracteristica);
in action?

Zend Framework: How and where to create custom routes in a 1.8+ application?

I've never created a custom route before, but I finally have a need for one. My question is: How can I create a custom route, and where should I create it? I'm using Zend Framework 1.9.6.
Here's how I did it. There may be a better way, but I think this is as simple as it gets:
# /application/Bootstrap.php
protected function _initSpecialRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$router->addRoute(
'verify', new Zend_Controller_Router_Route('verify/:token', array(
'controller' => 'account',
'action' => 'verify'))
);
$router->addRoute(
'arbitrary-route-name', new Zend_Controller_Router_Route('special/route/:variablename', array(
'controller' => 'defaultcontrollername',
'action' => 'defaultactionname'))
);
}
Here's where I define my custom routes:
// in bootstrap
Zend_Controller_Front::getInstance()->registerPlugin( new Prontiso_Controller_Plugin_Routes() );
...
<?php
// Prontiso/Controller/Plugin/Routes.php
class Prontiso_Controller_Plugin_Routes extends Zend_Controller_Plugin_Abstract
{
public function routeStartup( Zend_Controller_Request_Abstract $request )
{
Prontiso_Routes::addAll();
}
}
...
<?php
// Prontiso/Routes.php
class Prontiso_Routes extends Zend_Controller_Plugin_Abstract
{
public static function addAll()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
/**
* Define routes from generic to specific.
*/
/**
* Example: Simple controller/action route, guaranteed to eliminate any extra URI parameters.
*/
$router->addRoute(
'barebones', new Zend_Controller_Router_Route(':controller/:action')
);
}
}
See the Router documentation for the how. And I would make your routes in the bootstrap. Either program the routes by hand or load a configuration file.
I know there's already an accepted answer by #Andrew but I wanted to post this method that is very similar to his but I find cleaner.
If you dig into Zend_Controller_Router_Rewrite you will find an addRoutes() method which just iterates over each key and value and calls addRoute().
So here's my solution:
# /application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$loginRoute = new Zend_Controller_Router_Route('login', array('controller' => 'auth', 'action' => 'login'));
$logoutRoute = new Zend_Controller_Router_Route('logout', array('controller' => 'auth', 'action' => 'logout'));
$routesArray = array('login' => $loginRoute, 'logout' => $logoutRoute);
$router->addRoutes($routesArray);
}
}