PHP magic method __invoke, empty instance variables - php

I'm facing a problem when calling __invoke() on an object. Is __invoke() method agnostic to instance variables? I need to call __invoke() directly on my templates due to some ZF2 injection to call $this->getView()->render(...) (otherwise getView() returns null) and I would like to have instance variables setted there. Any workaround?
See my code:
namespace Person\Person\View\Helper;
use Zend\View\Helper\AbstractHelper;
class PersonShowWidget extends AbstractHelper
{
protected $model = null;
public function __construct(array $options = null)
{
$this->parseOptions($options);
}
public function __invoke()
{
var_dump($this->model); //returns null
return $this->getView()->render('person/show/show_widget', array(
'title' => 'Cliente',
'model' => $this->model,
)
);
}
public function setOptions(array $options = null)
{
$this->parseOptions($options);
}
protected function parseOptions(array $options = null)
{
if (!is_null($options) && is_array($options)) {
if (isset($options['model'])) {
$model = $options['model'];
if (isset($model['id'])) {
$this->model['id'] = $model['id'];
} else {
throw new \Exception;
}
if (isset($model['form'])) {
$this->model['form'] = $model['form'];
} else {
throw new \Exception;
}
}
}
var_dump($this->model); //returns valid data
}
}
I do have called the constructor with some options or the setOptions method before calling __invoke().
Thanks,

You have to initialize the view helper with a factory. In this way you can make sure the constructor is called before the __invoke method is called. And no..the __invoke() method is not agnostic to instance variables.
In the Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'personShowWidget' => function ($helpers) {
$array = array();
$helper = new Person\Person\View\Helper\PersonShowWidget($array);
return $helper;
},
)
);
}
Or in the module.config.php
'view_helpers' => array
(
'factories' => array(
'personShowWidget' => function ($helpers) {
$array = array();
$helper = new Person\Person\View\Helper\PersonShowWidget($array);
return $helper;
},
)
)
Performance-wise you'd better make a Factory class instead of a callable.
More info: http://framework.zend.com/manual/2.0/en/modules/zend.module-manager.module-manager.html
Edit:
It seems like you using the ViewHelper wrongly. You don't have to create the instance by yourself. Just use the ViewHelper in the view. So why not just give the $options as parameter to the __invoke method?
public function __invoke(array $options = null)
{
$this->setOptions($options);
return $this->getView()->render('person/show/show_widget', array(
'title' => 'Cliente',
'model' => $this->model,
)
);
}
In the Controller pass the options array to the view:
return array(
'options' => $options,
);
And call the ViewHelper in the view:
<?php echo $this->personShowWidget($this->options); ?>
Remember: In this way you don't need a Factory to init the ViewHelper. Just add it to the invokables.
module.config.php example:
'view_helpers' => array(
'invokables' => array(
'personShowWidget' => 'Person\Person\View\Helper\PersonShowWidget',
),
),

Related

Fatal error: Call to a member function get() on null in Zend Framework 2

I am getting null when using $sm=$this->getServiceLocator() as a result $sm->get("XXXXXXXXXXX") throwing a Fatal error: Call to a member function get() on null.
What i am doing is that, while receiving user data in controller i am calling another controller validatorController inside my requested controller which is signupController and in validatorController i am using $sm=$this->getServiceLocator() which gives the above error
Here is my work
Error comes when i use $check=$this->_getUserTable()->isUnique($email); in ValidatorController.php but not in SignupController.php
Module.php
<?php
namespace User;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\ResultSet\ResultSet;
use User\Controller\ValidatorController;
use User\Model\User;
use User\Model\UserTable;
class Module {
public function getConfig() {
return include __DIR__."/config/module.config.php";
}
public function getAutoloaderConfig() {
return array(
"Zend\loader\StandardAutoloader"=>array(
"namespaces"=>array(
__NAMESPACE__=>__DIR__."/src/".__NAMESPACE__
)
)
);
}
public function getServiceConfig() {
return array(
"factories"=>array(
'User\ValidatorController' => function ($sm) {
$validatorController = new ValidatorController();
return $validatorController;
},
"User\Model\UserTable"=>function($sm) {
$tableGateway=$sm->get("UserTableGateway");
$table=new UserTable($tableGateway);
return $table;
},
"UserTableGateway"=>function($sm) {
$dbAdapter=$sm->get("Zend\Db\Adapter\Adapter");
$resultSetPrototype=new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new User());
return new TableGateway("users",$dbAdapter,null,$resultSetPrototype);
}
)
);
}
}
module.config.php
<?php
return array(
"controllers"=>array(
"invokables"=>array(
"User\Controller\User"=>"User\Controller\UserController",
'User\Controller\Signup' => 'User\Controller\SignupController',
'User\Controller\Validator' => 'User\Controller\ValidatorController'
)
),
// The following section is new and should be added to your file
"router"=>array(
"routes"=>array(
"user"=>array(
"type"=>"segment",
"options"=>array(
"route" => "/user[/:action][/:id]",
"constraints" => array(
"id" => "[0-9]+",
),
"defaults"=>array(
"controller"=>"User\Controller\User"
)
)
),
'signup' => array(
'type' => 'segment',
'options' => array(
'route' => '/signup',
'defaults' => array(
'controller' => 'User\Controller\Signup',
)
)
),
)
),
'view_manager' => array(//Add this config
'strategies' => array(
'ViewJsonStrategy',
),
),
);
SignupController.php
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\View\Model\JsonModel;
class SignupController extends AbstractRestfulController{
private $_userTable;
public function create($data) {
/*
* The above error is not coming here
* $check=$this->_getUserTable()->isUnique($data['email']);
*
* But inside the below controller
*/
// Calling a validatorContoller
$validator=$this->getServiceLocator()->get('User\ValidatorController');
$response=$validator->validateEmail($data['email']);
return new JsonModel($response);
}
public function _getUserTable() {
if(!$this->_userTable) {
$sm=$this->getServiceLocator();
$this->_userTable=$sm->get("User\Model\UserTable");
}
return $this->_userTable;
}
}
ValidatorController.php
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\Validator\EmailAddress;
class ValidatorController extends AbstractRestfulController {
private $_userTable;
public function validateEmail($email) {
$validator = new EmailAddress();
if($validator->isValid($email)) {
// check if it is a unique entry in user table
// ***(THE SOURCE OF ERROR IS HERE)***
$check=$this->_getUserTable()->isUnique($email);
return $check;
}
}
public function _getUserTable() {
if(!$this->_userTable) {
$sm=$this->getServiceLocator();
$this->_userTable=$sm->get("User\Model\UserTable");
}
return $this->_userTable;
}
}
NOTE
Error comes when i use $check=$this->_getUserTable()->isUnique($email); in ValidatorController.php but not in SignupController.php
Thankyou
getServiceLocator() is deprecated in ZendFramework 2. You must inject _userTable in your ValidatorController from your Module.php like this :
class Module {
...
public function getServiceConfig() {
return array(
"factories"=>array(
'User\ValidatorController' => function ($sm) {
$userTable = $sm->get("User\Model\UserTable");
$validatorController = new ValidatorController();
$validatorController->setUserTable($userTable);
return $validatorController;
},
...
}
Then add a setUserTable() method in your ValidController and modify the getUserTable() method :
class ValidController {
public function setUserTable($suerTable) {
$this->_suerTable = $userTable
}
public function _getUserTable() {
return $this->_userTable;
}
}

Laravel Fractal transformer, how to pass and get extra variable

I'm using Dingo API to create an API in Laravel 5.2 and have a controller returning data with
return $this->response->paginator($rows, new SymptomTransformer, ['user_id' => $user_id]);
However, I don't know how to retrieve user_id value in the SymptomTransformer! Tried many different ways and tried looking into the class but I'm relatively new to both Laravel and OOP so if anyone can point me to the right direction, it'd be greatly appreciated.
Below is my transformer class.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
You can pass extra parameter to transformer constructor.
class SymptomTransformer extends TransformerAbstract
{
protected $extra;
public function __construct($extra) {
$this->extra = $exta;
}
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->extra);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
}
And call like
return $this->response->paginator($rows, new SymptomTransformer(['user_id' => $user_id]));
You can set extra param via setter.
class SymptomTransformer extends TransformerAbstract
{
public function transform(Symptom $row)
{
// need to get user_id here
dd($this->test_param);
return [
'id' => $row->id,
'name' => $row->name,
'next_type' => $next,
'allow' => $allow
];
}
public function setTestParam($test_param)
{
$this->test_param = $test_param;
}
}
And then:
$symptomTransformer = new SymptomTransformer;
$symptomTransformer->setTestParam('something');
return $this->response->paginator($rows, $symptomTransformer);
If you are using Dependency Injection, then you need to pass params afterwards.
This is my strategy:
<?php
namespace App\Traits;
trait TransformerParams {
private $params;
public function addParam() {
$args = func_get_args();
if(is_array($args[0]))
{
$this->params = $args[0];
} else {
$this->params[$args[0]] = $args[1];
}
}
}
Then you implement the trait in your transformer:
<?php
namespace App\Transformers;
use App\Traits\TransformerParams;
use App\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
use TransformerParams;
public function transform(User $user)
{
return array_merge([
'id' => (int) $user->id,
'username' => $user->username,
'email' => $user->email,
'role' => $user->roles[0],
'image' => $user->image
], $this->params); // in real world, you'd not be using array_merge
}
}
So, in your Controller, just do this:
public function index(Request $request, UserTransformer $transformer)
{
$transformer->addParam('has_extra_param', ':D');
// ... rest of the code
}
Basically, the trait is a bag for extra params.

Passing variables in phpunit

I'm trying to pass an array i created in my testcase into my function i want to test. Is it possible to create a vairable in the testcase and pass or mock it to the class/function i want to test?
is it possible to use something like this:
$this->object = array(//array code here);
$this->testclass->attachVar->getAllObjects($this->objects);
Here is my code:
class myClassTest extends \PHPUnit_Framework_TestCase {
protected function setUp(){
$this->testclass = new \stdClass();
$this->testclass = $this->getMockBuilder('library\ixPlanta\plantChange', $this->object)
->disableOriginalConstructor()
->getMock();
}
public function testGetAllObjects() {
$this->object = array(
'testdb' => array(
'testdb_michel' => array(
'dbname' => 'testdb',
'projectName' => 'testdb',
'projectID' => 'bd993d2b9478582f6d3b73cda00bd2a',
'mainProject' => 'test',
'search' => false,
'webgroup' => array(),
'locked' => false
)
)
);
$this->testclass->expects($this->once())
->method('GetAllObjects')
->with('testdb', false, "CHECKED")
->injectTo('object', $this->object)
->will();
$result = $this->testclass->getAllObjects('testdb', false, "CHECKED");
$this->assertTrue($result);
}
In the function testGetAllObjects() i created an array that i want to pass to the function i want to test
public function getAllObjects($company,$selected=false,$selectText='CHECKED'){
$objList = array();
$i = 0;
foreach($this->Objects[$company] as $key => $value){
$objList[$i] = array('value'=> $key,'name' => $value['projectName'], 'objectID' => $value['projectID']);
$objList[$i]['checked'] = '';
if($selected !== false && !is_array($selected) && $selected === $value['dbname']){
$objList[$i]['checked'] = $selectText;
}elseif($selected !== false && is_array($selected) && in_array($value['dbname'], $selected)){
$objList[$i]['checked'] = $selectText;
}
++$i;
}
return $objList;
}
The variable i want to pass to getAllObjects is $this->objects
I think you misunderstood mock objects. The purpose of mock objects is, to create a dummy object for any other class you don't want to test. Mocking a method means, to prevent another class from calling its actual logic. Instead, it is not executed and the mock just returns whatever you gave it.
To test your actual class you just instantiate it and call its method:
class myClassTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
$this->testclass = new MyClass();
}
public function testGetAllObjects()
{
$this->testclass->object = array(
'testdb' => array(
'testdb_michel' => array(
'dbname' => 'testdb',
'projectName' => 'testdb',
'projectID' => 'bd993d2b9478582f6d3b73cda00bd2a',
'mainProject' => 'test',
'search' => false,
'webgroup' => array(),
'locked' => false
)
)
);
$result = $this->testclass->getAllObjects('testdb', false, "CHECKED");
$this->assertTrue($result);
}
}
Example of a mock:
Let's say your class contains some other object of the class Service which is injected through the constructor:
class MyClass {
protected $service;
public function __construct(Service $service) {
$this->service = $service;
}
public function myMethod($argument) {
$return = $this->service->callService($argument);
return $return;
}
}
And your Service object is something like this:
class Service{
public function callService($argument) {
if($argument === NULL) {
throw new \Exception("argument can't be NULL");
}
return true;
}
}
Now you could test MyClass with this method:
public function testMyClassMethod() {
$serviceMock = $this->getMockBuilder("Service")->getMock();
$serviceMock->expects($this->any())
->method("callService")
->will($this->returnValue(true));
$myClass = new MyClass($serviceMock);
$this->assertTrue($myClass->myMethod(NULL));
}
myMethod will still return true, although Service would normally throw an Exception if $argument is NULL. But because we mocked the method, it is never actually called and the mock object will return whatever we provided in ->will($this->returnValue()).

ZF 2 - Fatal Error: Call to a member function getPosts() on null

I am creating a website using Zend Framework 2, and I'm using as an example the exercise from the official course of Zend Technology, Zend Framework 2: Fundamentals.
I have a table called posts and I want to show the table content in my home page, ordered by id. These are the codes I have written:
Controller/PostsTableTrait.php
trait PostsTableTrait
{
private $postsTable;
public function setPostsTable($postsTable)
{
$this->postsTable = $postsTable;
}
}
Controller/IndexController.php
class IndexController extends AbstractActionController
{
use PostsTableTrait;
public function indexAction()
{
return new ViewModel(array(
'post' => $this->postsTable->getPosts()
));
}
}
Factory/IndexControllerFactory.php
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator()->get('ServiceManager');
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}
Factory/PostsTableFactory.php
class PostsTableFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new PostsTable(PostsTable::$tableName, $serviceLocator->get('Zend\Db\Adapter\AdapterService'));
}
}
Model/PostsTable.php
class PostsTable extends TableGateway
{
public static $tableName = "posts";
public function getPosts()
{
$select = new Select(self::$tableName);
$select->columns(array(
'date',
'title',
'text',
'category'
));
$select->order('id DESC');
return $select;
}
}
config/module.config.php
'controllers' => array(
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\Panel' => 'Rxe\Controller\PanelController'
),
'factories' => array(
'Rxe\Factory\PanelController' => 'Rxe\Factory\PanelControllerFactory'
)
),
'service_manager' => array(
'factories' => array(
'Rxe\Factory\PanelForm' => 'Rxe\Factory\PanelFormFactory',
'Rxe\Factory\PanelFilter' => 'Rxe\Factory\PanelFilterFactory',
'Rxe\Factory\PostsTable' => 'Rxe\Factory\PostsTableFactory',
'Zend\Db\Adapter\AdapterService' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
I don't know if the error could be in the getPosts() method. I have tried many different ways to return the query but none of them made any difference, not even showed another error.
You have registered the controller as an 'invokable'. When the the controller manager creates IndexController it will do so without using the IndexControllerFactory; therefore the Rxe\Factory\PostsTable dependency is never set.
To fix this, update module.config.php and register the index controller with your factory class.
'controllers' => [
'factories' => [
'Rxe\Controller\Index' => 'Rxe\Factory\IndexControllerFactory',
],
],
Also (not an error as such) but the IndexControllerFactory calls ->get('ServiceManager') using the service manager.
You could update it to be like this.
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
// #var \Zend\ServiceManager\ServiceManager
$serviceManager = $controllerManager->getServiceLocator();
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}

Access Doctrine Entity Manager in custom Twig Extension in ZF2

I am using ZfcTwig module from https://github.com/ZF-Commons/ZfcTwig
Reference used: http://symfony.com/doc/current/cookbook/templating/twig_extension.html
Using above link, I was able to create a custom Twig Extension. In this class, I want to access the Entity Manager to fetch some data from database.
I am registering the custom extension via following code in module.config.php -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension'
)
)
But as I want to access a Doctrine Entity Manager in this class, I tried by creating a closure -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension' => function($sm) {
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
$ext = new Application\Extension\MyCustomTwigExtension();
$ext->setEntityManager($entityManager);
return $ext;
},
)
),
And this gives the error message as -
Catchable fatal error: Argument 1 passed to Twig_Environment::addExtension() must implement interface Twig_ExtensionInterface, instance of Closure given....
I have tried for same via factory -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension' => 'Application\Extension\MyCustomTwigExtensionFactory'
)
),
Error message -
Catchable fatal error: Argument 1 passed to Twig_Environment::addExtension() must implement interface Twig_ExtensionInterface, instance of Application\Extension\MyCustomTwigExtensionFactory given...
Any ideas on injecting dependency into twig extension? It may not be just Entity Manager but access to View Helpers, etc.
Edit: Contents of MyCustomTwigExtension file -
namespace Application\Extension;
class MyCustomTwigExtension extends \Twig_Extension {
protected $_eventManager;
public function setEntityManager($eventManager = null) {
$this->_eventManager = $eventManager;
}
public function getEntityManager() {
return $this->_eventManager;
}
public function getFilters() {
return array(
new \Twig_SimpleFilter('list', array($this, 'listFilter')),
);
}
public function listFilter($content = '') {
$repository = $this->getEntityManager()->getRepository('Application\Entity\User');
$models = $repository->findAll();
$html = '<ul>List Extension';
foreach ($models as $model) {
$html .= "<li>" . $model->getId() . ' ' . $model->getFullname() . "</li>";
}
$html .= '</ul>';
return $html;
}
public function getName() {
return 'list_extension';
}
}
Thanks.
The only problem you have is the following:
As mentioned in module.config.php#32 you cann add Extension via Factory, but the given namespace Application\Extension\MyCustomTwigExtensionFactory must exist as key in your service_manager-config.
So you could do the following:
'zfctwig' => array(
'extensions' => array(
'customTwig' => 'MyCustomTwigExtensionFactory'
)
),
'service_manager' => array(
'factories' => array(
'MyCustomTwigExtensionFactory' => 'Application\Extension\MyCustomTwigExtensionFactory'
)
)
And everything should be ok.
You can implement ServiceLocatorAwareInterface:
class MyCustomTwigExtension extends \Twig_Extension implements ServiceLocatorAwareInterface {
}
for implementation you can use trait: ServiceLocatorAwareTrait
and you can add method to get entity manager:
public function getEntityManager() {
if($this->entityManager === null) {
$this->entityManager = $this->getServiceLocator()->get(''doctrine.entitymanager.orm_default'')
}
return $this->entityManager;
}
and last thing: in configuration you should add:
'service_manager' => array(
'factories' => array(
'MyCustomTwigExtensionFactory' => function() { return new MyCustomTwigExtension(); }
)
)

Categories