Why my Zend-HAL implementation is not working with protected values - zend-framework

I am new in Zend framework, and trying to use HAL for API response generation. In the following is a simpler situation of my issues.
The class:
class Version
{
protected $data;
public function __construct($ar){
$data = $ar;
}
public function getArrayCopy(){
return $data;
}
}
$obj = new version(['major'=>1,'minor'=>2,'fix'=>3]);
When I test with hydrator, it works well as per the following:
use Zend\Hydrator\ArraySerializableHydrator;
$hydrator = new ArraySerializableHydrator();
$data = $hydrator->extract($obj);
print_r($data); // outputs ['major'=>1,'minor'=>2,'fix'=>3]
My HAL configuration is following:
MetadataMap::class => [
[
'__class__' => RouteBasedResourceMetadata::class,
'resource_class' => Version::class,
'route' => 'version',
'extractor' => ArraySerializableHydrator::class,
],
]
I use the following line in my Zend expressive (version 3) request handler
$resource = $this->resourceGenerator->fromObject($obj, $request);
$res = $this->responseFactory->createResponse($request, $resource);
The link is generated correctly, but the meta data (version info) is coming as empty. Any help will be much appreciated.
N.B.: My real code is complex, here I tried to generate a simpler version of the issue.

I think that when generating response the hydrate method is called. So your test does not seem to test what you meant to test.
When hydrating the hydrator works with ReflectionClass. So you need to add the indexes from $data as properties in the Version class.
e.g.
class Version
{
protected $major;
protected $minor;
protected $fix;
public function __construct($data){
foreach($data as $key => $value) {
$this->{$key} = $value;
}
}
public function getArrayCopy(){
return [
'major' => $this->major,
'minor' => $this->minor,
'fix' => $this->fix
];
}
}
$obj = new version(['major'=>1,'minor'=>2,'fix'=>3]);

Related

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.

How to merge subform array into single array in Zend Framework1.11

I am using Zend Framework1.11. In my Zend Form I have two zend sub form, I have added these two sub form using addSubForm function.
Now when I call this zend form in controller then isValid function is not working. I have called it as follow..
public function registeredAction(){
$form = new Application_Form_RegisteredForm();
$form->setAction('registered');
$formData = $this->_request->getPost();
if($form->isValid($formData)){
// save into database using model class.
} else {
$form->populate($formData);
}
$this->view->form = $form;
}
In following code isValid is not working, while I print_r the $fotmData requested array, it print array like:-
Array(
[personal] => Array
(
[firstname] => 'Example',
[lastname] => 'Solution'
)
[MAX_FILE_SIZE] => 8388608
[address] => Array
(
[country] => 'IND',
[state] => 'RAJ'
)
);
I have also used the setData() function but it is not working, it's give exceptional error "Message: Method setData does not exist", I have used php array_merge function but return array is not working with isValid().
Can anyone help me to solve this problem. so I can easily store form data into database.
Thanks!
Take a look at array_merge
http://php.net/manual/de/function.array-merge.php
$newFormData=array_merge($formData["personal"],$formData["address"]);
My solution is to create new base form with new method getSubFormsValues(), i.e.:
class My_Form extends Zend_Form
{
public function getSubFormsValues()
{
$values = array();
foreach ($this->getSubForms() as $form) {
$name = $form->getName();
$value = $form->getValues();
$values = array_merge($value[$name], $values);
}
return $values;
}
}
When you can call $my_form_obj->getSubFormValues() in your code.

Zend Framework Router Hostname and Multi-Language support

Last two days I was fighting with Zend Framework Router and still didn't find solution.
Existing project has 2 different modules which work with the same domain e.g. www.domain1.com:
default - Contains public information, can be accessed via www.domain1.com
admin - Administrator interface, can be accessed via www.domain1.com/admin
Project is multi-langual and to keep language code it is transmitted as first parameter of every URL, e.g. www.domain1.com/en/ www.domain1.com/en/admin
Part of code which takes care is next plugin:
class Foo_Plugin_Language extends Zend_Controller_Plugin_Abstract
{
const LANGUAGE_KEY = 'lang';
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$languagesRegexp = implode('|', array_map('preg_quote', $this->_bootstrap->getLanguages()));
$routeLang = new Zend_Controller_Router_Route(
':' . self::LANGUAGE_KEY,
array(self::LANGUAGE_KEY => $this->_bootstrap->getLanguage()->toString()),
array(self::LANGUAGE_KEY => $languagesRegexp)
);
$router = $this->getFrontController()->getRouter();
$router->addDefaultRoutes();
$chainSeparator = '/';
foreach ($router->getRoutes() as $name => $route) {
$chain = new Zend_Controller_Router_Route_Chain();
$chain
->chain($routeLang, $chainSeparator)
->chain($route, $chainSeparator);
$new_name = $this->_formatLanguageRoute($name);
$router->addRoute($new_name, $chain);
}
protected function _formatLanguageRoute($name)
{
$suffix = '_' . self::LANGUAGE_KEY;
if (substr($name, -strlen($suffix)) == $suffix) return $name;
return $name . '_' . self::LANGUAGE_KEY;
}
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam(self::LANGUAGE_KEY, null);
$this->_bootstrap->setLanguage($lang);
$actual_lang = $this->_bootstrap->getLanguage()->toString();
$router = $this->getFrontController()->getRouter();
$router->setGlobalParam(self::LANGUAGE_KEY, $lang);
// Do not redirect image resize requests OR get js, css files
if (preg_match('/.*\.(jpg|jpeg|gif|png|bmp|js|css)$/i', $request->getPathInfo())) {
return true;
}
// redirect to appropriate language
if ($lang != $actual_lang) {
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$params = array(self::LANGUAGE_KEY => $actual_lang);
$route = $this->_formatLanguageRoute($router->getCurrentRouteName());
return $redirector->gotoRouteAndExit($params, $route, false);
}
}
}
One of the first question is what do you think about such way to provide multi-lang support. What I've noticed is that all this chains dramatically decrease operation speed of the server, response time from the server is about 4 seconds...
Main question is: Currently I have to implement such feature: I have domain www.domain2.com that should work just with single module e.g. "foobar"... and it should be available via second url... or, of course, it should work like www.domain1.com/en/foobar by default...
To provide this functionality in Bootstrap class I'be implemented such part of code
// Instantiate default module route
$routeDefault = new Zend_Controller_Router_Route_Module(
array(),
$front->getDispatcher(),
$front->getRequest()
);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobarHostname", $foobarHostname->chain($routeDefault));
And that is not working and as I've found routeDefault always rewrite found correct model name "foobar" with value "default"
Then I've implemented default router like this:
new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'controller' => 'index',
'action' => 'index'
);
);
But that still didn't work, and started work without language only when I comment "routeStartup" method in Foo_Plugin_Language BUT I need language support, I've played a lot with all possible combinations of code and in the end made this to provide language support by default:
class Project_Controller_Router_Route extends Zend_Controller_Router_Route_Module
{
/**
* #param string $path
* #param bool $partial
* #return array
*/
public function match($path, $partial = false)
{
$result = array();
$languageRegexp = '%^\/([a-z]{2})(/.*)%i';
if (preg_match($languageRegexp, $path, $matches)) {
$result['lang'] = $matches[1];
$path = $matches[2];
}
$parentMatch = parent::match($path);
if (is_array($parentMatch)) {
$result = array_merge($result, $parentMatch);
}
return $result;
}
}
So language parameter was carefully extracted from path and regular processed left part as usual...
But when I did next code I was not able to get access to the foobar module via www.domain2.com url, because of module name in request was always "default"
$front = Zend_Controller_Front::getInstance();
/** #var Zend_Controller_Router_Rewrite $router */
$router = $front->getRouter();
$dispatcher = $front->getDispatcher();
$request = $front->getRequest();
$routerDefault = new Project_Controller_Router_Route(array(), $dispatcher, $request);
$router->removeDefaultRoutes();
$router->addRoute('default', $routerDefault);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobar", $foobarHostname->chain($routerDefault));
Instead of summary:
Problem is that I should implement feature that will provide access for the secondary domain to the specific module of ZendFramework, and I should save multi-language support. And I cannot find a way, how to manage all of this...
Secondary question is about performance of chain router, it makes site work very-very slow...
The way I have solved problem with multilanguage page is in this thread:
Working with multilanguage routers in Zend (last post).
Ofcourse my sollution need some caching to do, but I think it will solve your problem.
Cheers.

CakePHP - Using a different model in current model

I am creating a custom validation function in my model in CakePHP. After reading similar questions I have understood that I could be using ClassRegistry::init('Model'); to load a foreign model in my current model. But it doesn't say much more on the syntax and how to actually use it afterwards. This is what I have tried, but nothing "is happening" when I am trying to print the array to see if it contains the right stuff. Basically I want to pull out the User data to use it in my validation.
class Booking extends AppModel {
public $name = 'Booking';
public $validate = array(
'start_time' => array(
'noOptionViolation' => array(
'rule' => 'noOptionViolation',
'allowEmpty' => false
)
),
);
public function noOptionViolation ($start_time) {
$this->User = ClassRegistry::init('User');
$allUsers = $this->User->find('all');
print_r($allUsers);
}
Is this correct syntax? Can I use all the methods of $this->User just like I would in a controller?
You can use import as detailed on this post:
https://stackoverflow.com/a/13140816/1081396
App::import('Model', 'SystemSettings.SystemSetting');
$settings = new SystemSetting();
$mySettings = $settings->getSettings();
In your example it would be like:
App::import('Model', 'Users.User');
$user = new User();
$allUsers = $user->find('all');
print_r($allUsers);
You could better use the import at the beginning of the model.
You could use this too to load Models
$this->loadModel('User');
and access all functions by
$this->User

Unit testing forms that have a hash element in Zend Framework

I'm trying to test valid form data in one of my Zend_Forms however it is failing due to it having a hash element that is generated randomly and I cannot access the generated hash to put it back into the assertion data. E.g.
$form = new MyForm();
$data = array('username'=>'test');
$this->assertTrue($form->isValid($data));
This fails as it doesn't contain the hash element value.
I had same problem when my form had captcha and I wanted to test it. Two possible solutions that I cant think about:
First render the form (hash will be generated then), then take that element, take value and use it to test form.
Just remove hash element for testing.
Thanks singles. Rendering the form before the tests worked perfectly for my problem. I wouldn't be too happy about removing the hash element for testing as:
You'll be adding a certain amount of
pointless code to remove these
elements during testing.
Security features need to be tested too.
Here's a quick example of how I did it:
public function testLoginSetsSession()
{
// must render the form first to generate the CSRF hash
$form = new Form_Login();
$form->render();
$this->request
->setMethod('POST')
->setPost(array(
'email' => 'test#test.co.uk',
'password' => 'password',
'hash' => $form->hash->getValue()
));
$this->dispatch('/login');
$this->assertTrue(Zend_Auth::getInstance()->hasIdentity());
}
I recently found a great way of testing forms with hash elements. This will use a mock object to stub away the hash element and you won't have to worry about it. You won't even have to do a session_start or anything this way. You won't have to 'prerender' the form either.
First create a 'stub' class like so
class My_Form_Element_HashStub extends Zend_Form_Element_Hash
{
public function __construct(){}
}
Then, add the following to the form somewhere.
class MyForm extends Zend_Form{
protected $_hashElement;
public function setHashElement( Zend_Form_Hash_Element $hash )
{
$this->_hashElement = $hash;
return $this;
}
protected function _getHashElement( $name = 'hashElement' )
{
if( !isset( $this->_hashElement )
{
if( isset( $name ) )
{
$element = new Zend_Form_Element_Hash( $name,
array( 'id' => $name ) );
}
else
{
$element = new Zend_Form_Element_Hash( 'hashElement',
array( 'id' => 'hashElement' ) );
}
$this->setHashElement( $element );
return $this->_hashElement;
}
}
/**
*
* In your init method you can now add the hash element like below
*
*
*
*/
public function init()
{
//other code
$this->addElement( $this->_getHashElement( 'myotherhashelementname' );
//other code
}
The set method is there just for testing purposes really. You probably won't use it at all during real use but now in phpunit you can right the following.
class My_Form_LoginTest
extends PHPUnit_Framework_TestCase
{
/**
*
* #var My_Form_Login
*/
protected $_form;
/**
*
* #var PHPUnit_Framework_MockObject_MockObject
*/
protected $_hash;
public function setUp()
{
parent::setUp();
$this->_hash = $this->getMock( 'My_Form_Element_HashStub' );
$this->_form = new My_Form_Login( array(
'action' => '/',
'hashElement' => $this->_hash
}
public function testTrue()
{
//The hash element will now always validate to true
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
//OR if you need it to validate to false
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
}
You HAVE to create your own stub. You can't just call the phpunit method getMockObject because that will directly extend the hash element and the normal hash element does 'evil' stuff in its constructor.
With this method you don't even need to be connected to a database to test your forms! It took me a while to think of this.
If you want, you can push the setHashElement method ( along with the variable and the get method ) into some FormAbstract base class.
REMEMBER, in phpunit you HAVE to pass the hash element during form construction. If you don't, your init method will get called before your stub hash can be set with the set method and you'll end up using the regular hash element. You'll know you're using the regular hash element because you'll probably get some session error if you're NOT connected to a database.
Let me know if you find this helpful or if you use it.