Zend variable not accessible in view - zend-framework

I have my own abstract class that extends Zend_Controller_Action and all my controllers then extend this class. Here is my abstract class:
<?php
abstract class CLG_Controller_Action extends Zend_Controller_Action
{
public $admin;
public $staff;
public $pool;
public $it;
//public $staff;
/**
*
* #var HTMLPurifier
*/
public $purifier;
public $action;
public $controller;
public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array())
{
parent::__construct($request, $response, $invokeArgs);
if( Zend_Registry::isRegistered('admin') ) {
$this->admin = Zend_Registry::get('admin');
}
if( Zend_Registry::isRegistered('staff') ) {
$this->staff = Zend_Registry::get('staff');
}
if( Zend_Registry::isRegistered('pool') ) {
$this->pool = Zend_Registry::get('pool');
}
$this->purifier = Zend_Registry::get('purifier');
$this->controller = $this->getRequest()->getControllerName();
$this->action = $this->getRequest()->getActionName();
$this->registerViewObjects();
}
public function postDispatch()
{
/************************************************
* Prepare JS and CSS FILES FOR THIS REQUEST
************************************************/
$action = $this->_request->getActionName();
$controller = $this->_request->getControllerName();
$this->view->headScript()->appendFile('/js/jquery-2.0.2.min.js');
if (key_exists ( $this->_request->getActionName (), $this->assets ))
{
$action = $this->_request->getActionName ();
foreach ( $this->assets [$action] ['css'] as $css )
{
$this->view->headLink()->appendStylesheet ( $css , 'print');
}
foreach ( $this->assets [$action] ['js'] as $js )
{
$this->view->headScript()->appendFile( $js );
}
}
$css = '/css/' . $controller . '/' . $action . '.css';
$js = '/js/' . $controller . '/' . $action . '.js';
$this->view->headLink()->appendStylesheet ( $css , 'print');
$this->view->headScript()->appendFile( $js );
}
private function registerViewObjects()
{
// THESE ARE ALWAYS AVAILABLE IN THE VIEW
$this->view->admin = $this->admin;
$this->view->staff = $this->staff;
$this->view->pool = $this->pool;
$this->view->controller = $this->controller;
$this->view->action = $this->action;
$this->view->purifier = $this->purifier;
}
}
However, for some reason, the variables registered in the registerViewObjects() are not accessible in my view files.
What am I missing here?
Thanks
UPDATE:
I should say that I have another class ActionMenu that extends Action, and my controllers then extend that class!

Is there a reason you are using __construct over init()? I'm pretty sure this is the reason of your problem because Zend performs various actions regarding the request, action etc in the __construct() stage.
/**
* #return void
*/
public function init()
{
if( Zend_Registry::isRegistered('admin') ) {
$this->admin = Zend_Registry::get('admin');
}
if( Zend_Registry::isRegistered('staff') ) {
$this->staff = Zend_Registry::get('staff');
}
if( Zend_Registry::isRegistered('pool') ) {
$this->pool = Zend_Registry::get('pool');
}
$this->purifier = Zend_Registry::get('purifier');
$this->controller = $this->getRequest()->getControllerName();
$this->action = $this->getRequest()->getActionName();
$this->registerViewObjects();
}
See also:
http://framework.zend.com/manual/1.12/en/zend.controller.action.html#zend.controller.action.initialization

Seeing as you're attempting to use the $view property so early in the controller lifecycle, maybe you just need to initialise it before putting values in, eg
private function registerViewObjects() {
$this->initView();
// and the rest
See http://framework.zend.com/manual/1.12/en/zend.controller.action.html#zend.controller.action.viewintegration

Related

Prestashop development - how configure a Controller (Property Tab->name is empty)

I want to set up a Controller following this guide:
https://webkul.com/blog/create-modules-admin-controllers-without-creating-tab-prestashop/
So in my custom module I do this:
....
public function install() {
return (parent::install()
&& $this->registerHook('header')
&& $this->registerHook('footer')
&& $this->installTab()
);
}
public function installTab() {
$tab = new Tab();
$tab->active = 1;
$tab->class_name = 'abandonedCartsAdminModuleController';
$tab->name = "test";
//If you don't want to create a tab for your admin controller then Pass id_parent value as -1.
$tab->id_parent = -1;
$tab->module = $this->name;
return $tab->add();
}
This is the Controller: abandonedCartsAdminModuleController.php
<?php
class abandonedCartsAdminModuleController extends AdminModuleController {
public function __construct() {
parent::__construct();
$this->context = Context::getContext();
}
public function init() {
$this->retrieve();
}
public function retrieve() {
...
}
}
What happens when I try to install my module is I have the PrestaShopException: "Property Tab->name is empty
at line 887 in file classes/ObjectModel.php"
$tab->name must be an array, one name by language.
$tab->name = array();
foreach (Language::getLanguages(true) as $lang) {
$tab->name[$lang['id_lang']] = 'test';
}

phpunit: How do I pass values between tests?

I'm really running into a brick wall with this. How do you pass class values between tests in phpunit?
Test 1 -> sets value,
Test 2 -> reads value
Here is my code:
class JsonRpcBitcoinTest extends PHPUnit_Framework_TestCase
{
public function setUp(){
global $configRpcUser, $configRpcPass, $configRpcHost, $configRpcPort;
$this->bitcoindConn = new JsonRpcBitcoin($configRpcUser, $configRpcPass, $configRpcHost, $configRpcPort);
$this->blockHash = '';
}
/**
* #depends testCanAuthenticateToBitcoindWithGoodCred
*/
public function testCmdGetBlockHash()
{
$result = (array)json_decode($this->bitcoindConn->getblockhash(20));
$this->blockHash = $result['result'];
$this->assertNotNull($result['result']);
}
/**
* #depends testCmdGetBlockHash
*/
public function testCmdGetBlock()
{
$result = (array)json_decode($this->bitcoindConn->getblock($this->blockHash));
$this->assertEquals($result['error'], $this->blockHash);
}
}
testCmdGetBlock() is not getting the value of $this->blockHash that should be set in testCmdGetBlockHash().
Help in understanding what is wrong would be greatly appreciated.
The setUp() method is always called before tests, so even if you set up a dependency between two tests, any variables set in setUp() will be overwritten. The way PHPUnit data passing works is from the return value of one test to the parameter of the other:
class JsonRpcBitcoinTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
global $configRpcUser, $configRpcPass, $configRpcHost, $configRpcPort;
$this->bitcoindConn = new JsonRpcBitcoin($configRpcUser, $configRpcPass, $configRpcHost, $configRpcPort);
$this->blockHash = '';
}
public function testCmdGetBlockHash()
{
$result = (array)json_decode($this->bitcoindConn->getblockhash(20));
$this->assertNotNull($result['result']);
return $result['result']; // the block hash
}
/**
* #depends testCmdGetBlockHash
*/
public function testCmdGetBlock($blockHash) // return value from above method
{
$result = (array)json_decode($this->bitcoindConn->getblock($blockHash));
$this->assertEquals($result['error'], $blockHash);
}
}
So if you need to save more state between tests, return more data in that method. I would guess that the reason PHPUnit makes this annoying is to discourage dependent tests.
See the official documentation for details.
You can use a static variable within a function...
PHP annoyingly shares static variables of class methods with all the instances... But in this cas it can help :p
protected function &getSharedVar()
{
static $value = null;
return $value;
}
...
public function testTest1()
{
$value = &$this->getSharedVar();
$value = 'Hello Test 2';
}
public function testTest2()
{
$value = &$this->getSharedVar();
// $value should be ok
}
NB: this is NOT the good way but it helps if you need some data in all your tests...
Another option is to use static variables.
Here is an example (for Symfony 4 functional tests):
namespace App\Tests\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
use Symfony\Component\HttpFoundation\AcceptHeader;
class BasicApiTest extends WebTestCase
{
// This trait provided by HautelookAliceBundle will take care of refreshing the database content to a known state before each test
use RefreshDatabaseTrait;
private $client = null;
/**
* #var string
*/
private const APP_TOKEN = 'token-for-tests';
/**
* #var string
*/
private static $app_user__email = 'tester+api+01#localhost';
/**
* #var string
*/
private static $app_user__pass = 'tester+app+01+password';
/**
* #var null|string
*/
private static $app_user__access_token = null;
public function test__Authentication__Login()
{
$this->client->request(
Request::METHOD_POST,
'/api/login',
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_App-Token' => self::APP_TOKEN
],
'{"user":"'.static::$app_user__email.'","pass":"'.static::$app_user__pass.'"}'
);
$response = $this->client->getResponse();
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
$content_type = AcceptHeader::fromString($response->headers->get('Content-Type'));
$this->assertTrue($content_type->has('application/json'));
$responseData = json_decode($response->getContent(), true);
$this->assertArrayHasKey('token', $responseData);
$this->static = static::$app_user__access_token = $responseData['token'];
}
/**
* #depends test__Authentication__Login
*/
public function test__SomeOtherTest()
{
$this->client->request(
Request::METHOD_GET,
'/api/some_endpoint',
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_App-Token' => self::APP_TOKEN,
'HTTP_Authorization' => 'Bearer ' . static::$app_user__access_token
],
'{"user":"'.static::$app_user__email.'","pass":"'.static::$app_user__pass.'"}'
);
$response = $this->client->getResponse();
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
$content_type = AcceptHeader::fromString($response->headers->get('Content-Type'));
$this->assertTrue($content_type->has('application/json'));
//...
}
}
Another (simpler) example using static variables as pointed out by BoĊĦtjan Biber:
class ClientTest extends \PHPUnit\Framework\TestCase
{
protected $client;
protected static $testClient;
// Before a test method is run, a template method called setUp() is invoked.
public function setUp() :void
{
$this->client = new \Application\models\Clients;
}
public function testInsertCustomer()
{
$testclient = array(
'name' => 'Test Client',
'responsible' => 'Responsible Test',
'payment' => 'Test payment',
'email' => 'Test Email',
'phone' => '123-456-789'
);
$this->assertTrue($this->customer->insertCustomer($CustomerTest));
}
public function testGetCustomers()
{
$this->assertIsArray($this->customer->getCustomers());
$this->assertNotEmpty($this->customer->getCustomers());
// Save test client for other methods
$clients = $this->client->getClients();
static::$testCustomer = end($customers);
}
public function testGetClient()
{
$this->assertIsArray($this->customer->getCustomer(static::$customerTest['customer_id']));
$this->assertNotEmpty($this->customer->getCustomer(static::$customerTest['customer_id']));
}
}
You can use a static variable using the method setUpBeforeClass:
protected static $sharedId;
public static function setUpBeforeClass(): void
{
self::$sharedId = random_int(100,10000);
}
And access it in you tests like this:
public function testCreateLocation() {
echo 'Shared variable = ' . self::$sharedId;
// Use the variable in your test code and asserts...
}
/The Schwartz
This worked for me perfectly fine across all tests: $this->varablename
class SignupTest extends TestCase
{
private $testemail = "registerunittest#company.com";
private $testpassword = "Mypassword";
public $testcustomerid = 123;
private $testcountrycode = "+1";
private $testphone = "5005550000";
public function setUp(): void
{
parent::setUp();
}
public function tearDown(): void
{
parent::tearDown();
}
public function testSignup()
{
$this->assertEquals("5005550000", $this->testphone;
}
}

zend: parameter collision

I wonder why no one ever asked this question.
Every zend Action function in controller class has 3 paramters, namely 'module', 'controller', and 'action'.
What happens, when I get a parameter named 'action' from a form or url, for example "?action=edit" ??
I tested it: action holds its value from router, not 'edit'.
public function someAction() {
$params = $this->getRequest()->getParams();
...
How could I pass the parameter named "action", if I had to ??
Thanks in advance.
The default route is Zend_Controller_Router_Route_Module which uses default keys for module, controller, & action:
protected $_moduleKey = 'module';
protected $_controllerKey = 'controller';
protected $_actionKey = 'action';
// ...
/**
* Set request keys based on values in request object
*
* #return void
*/
protected function _setRequestKeys()
{
if (null !== $this->_request) {
$this->_moduleKey = $this->_request->getModuleKey();
$this->_controllerKey = $this->_request->getControllerKey();
$this->_actionKey = $this->_request->getActionKey();
}
if (null !== $this->_dispatcher) {
$this->_defaults += array(
$this->_controllerKey => $this->_dispatcher->getDefaultControllerName(),
$this->_actionKey => $this->_dispatcher->getDefaultAction(),
$this->_moduleKey => $this->_dispatcher->getDefaultModule()
);
}
$this->_keysSet = true;
}
/**
* Matches a user submitted path. Assigns and returns an array of variables
* on a successful match.
*
* If a request object is registered, it uses its setModuleName(),
* setControllerName(), and setActionName() accessors to set those values.
* Always returns the values as an array.
*
* #param string $path Path used to match against this routing map
* #return array An array of assigned values or a false on a mismatch
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$values[$this->_controllerKey] = array_shift($path);
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
You can see that the default module route has default keys for mvc params, however, it will use the keys set by the request object if it exists and we can modify these keys.
e.g. in your bootstrap:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequestKeys()
{
$this->bootstrap('frontcontroller');
$frontController = $this->getResource('frontcontroller');
/* #var $frontController Zend_Controller_Front */
$request = new Zend_Controller_Request_Http();
// change action key
$request->setActionKey("new_action_key");
// change module
$request->setModuleKey("new_module_key");
// change controller
$request->setControllerKey("new_controller_key");
// don't forget to set the configured request
// object to the front controller
$frontController->setRequest($request);
}
}
Now you can use module, controller, & action as $_GET params.
After a little testing it seems that how you pass the key "action" matters.
If you try and pass a parameter named "action" with $this->_request->getParams() you will get the controller action value key pair.
If you pass the "action" key from a form with $form->getValues() you will retrieve the value from the form element named "action".
As with so many things, your use case determines how you need to handle the situation.
Good Luck.

Symfony2: Delete entity through post request (improvements required)

I need some improvements about my actual way to delete entities:
public function deleteAction($path)
{
$form = $this->createFormBuilder(array('path' => $path))
->add('path')
->setReadOnly(true)
->getForm();
if ($this->getRequest()->getMethod() === 'POST') {
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
$image = $this->getImageManager()->findImageByPath($path);
$this->getImageManager()->deleteImage($image);
return $this->redirect($this->generateUrl('AcmeImageBundle_Image_index'));
}
}
return $this->render('AcmeImageBundle:Image:delete.html.twig', array(
'form' => $form->createView(),
));
}
Two improvements I already found while writting:
CreateFormBuilder in extra method in controller
Hidden field and overgive extra image-entity to get rendered
Are there other thing I could make better?
Regards
(my answer is too long for the comment so i add it here)
First you have to create a Type file (generally in YourApp\YourBundle\Form\yourHandler.php), some basique code to put inside if you don't know:
<?php
namespace ***\****Bundle\Form;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
use ***\****Bundle\Entity\your_entity;
class *****Handler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
$this->onSuccess($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(your_entity $object)
{
// Make your stuff here (remove,....)
}
}
And in your controller i just call it this way:
if (!empty($_POST))
{
$formHandler = new *****Handler($my_form, $this->get('request'), $this->getDoctrine()->getEntityManager());
$formHandler->process();
}
Hope i'm clear enough

Zend Framework view script not containing variables from parent class

I created a parent CRUD class for several controllers and when I render the script it isn't recognizing the paginator variable I set within the listAction(). The code is from my parent class. For instance, I extend Admin_UserController to create Webapp_Controller_Crud.
class Webapp_Controller_Crud extends Zend_Controller_Action
{
public function init()
{
$actionController = get_class($this);
$actionController = str_replace('Admin_',null,$actionController);
$actionController = str_replace('Controller',null,$actionController);
$this->_actionClassName = $actionController;
$actionController = 'Model_' . $this->_actionClassName;
$this->_actionModel = new $actionController();
}
/**
* #return Zend_Paginator
*/
public function getPaginator()
{
$model = $this->getActionModel();
$adapter = new Zend_Paginator_Adapter_DbSelect($model->select());
$paginator = new Zend_Paginator($adapter);
$paginator->setItemCountPerPage(10);
$page = $this->_request->getParam('page', 1);
$paginator->setCurrentPageNumber($page);
return $paginator;
}
public function listAction()
{
$this->view->paginator = $this->getPaginator();
}
}
You use
$this->getView()->paginator = $this->getPaginator();
Which should be
$this->view->paginator = $this->getPaginator();