I searched for the same and got some answer about it, but did not help.
Those way I tried are as follows:
1)
in onBootstrap($e) function:
$e->getViewModel()->setVariable('test_variable', 'Hello World!');
//or
$e->getViewModel()->test_variable = 'Hello World!';
2)
I created a function in Module.php
function boforeDispatch(MvcEvent $event) {
$event->getViewModel()->setVariable('test_variable', 'Hello World!');
//or
$event->getViewModel()->test_variable = 'Hello World!';
}
and called this function inside the onBootstrap($e) function
$application = $e->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'boforeDispatch'), 100);
when I tried in view
echo $test_variable;
it gives undefined variable test_variable in ... error all the times.
Any idea?
Try using render event (MvcEvent::EVENT_RENDER) instead:
public function onBootstrap($event){
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$events->attach(MvcEvent::EVENT_RENDER, array($this, 'addVariablesToViewModel'), 100);
}
public function addVariablesToViewModel($event)
{
$viewModel = $this->getEvent()->getViewModel();
$viewModel->setVariables(array(
'key' => 'value',
));
}
Your view model is probably not yet available on (pre) dispatch
Related
In Zend Framework 3, is it possible to disable the layout for an entire controller, preferably in the __construct() or onDispatch() methods?
I know that I can disable the layout for specific actions, for example:
public function indexAction()
{
$view = new \Zend\View\Model\ViewModel();
$view->setTerminal(true);
return $view;
}
However, I would like to disable the layout for all actions in the controller without having to copy and paste the above code in every action.
In your Module class :
public function onBootstrap(MvcEvent $e)
{
$sharedEvents = $e->getApplication()
->getEventManager()
->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch',
function ($e) {
if ($e->getRouteMatch()->getParam('controller') == '[your controller name in lowercase]') {
$result = $e->getResult();
if ($result instanceof \Zend\View\Model\ViewModel) {
$result->setTerminal(true);
} else {
throw new \Exception(
__METHOD__ . ' expected \Zend\View\Model\ViewModel');
}
}
});
}
So, I have something like this:
class ClassA{
public function doCallback($callback){
call_user_func_array($callback, array());
}
public function doSomething(){
return 12345;
}
}
class ClassB{
public function runMe(){
$classA = new ClassA();
$classA->doCallback(function(){
$this->doSomething();
});
}
}
I am trying to figure out how, if possible I can use $this or something similar in a callback function that will refer to the class that the callback is running on (Not the class it is in) if that makes sense.
So in my above example I would like $this->doSomething(); where $this means ClassA and not ClassB. Currently $this is referring to ClassB. Is there something that I can use to say I want ClassA?
EDIT
Here is the actual method that I am using
public function save(){
$id = (int)$this->input->post("id");
$title = $this->input->post("title");
$uid = (int)$this->session->get("id");
$this->db->getTable("content")
->has(array("user_id" => $uid, "name" => $title), function(){
echo json_encode(array("error" => "Name already exists."));
}, function() use($id, $uid, $title){
//$this->db->getTable("content")
$this->update(array("name" => $title), array(
"user_id" => $uid,
"content_id" => $id), function($rows){
if($rows > 0){
$error = "";
}else{
$error = "Unknown error occurred";
}
echo json_encode(array("error" => $error));
});
});
}
$this->db->getTable("content") returns a Database Object, and has() is a method in the object. I was hoping that I could use a shorthand way to access $this->db->getTable("content") without having to call it again in the callback, or passing it through call_user_func_array as a parameter or without the use of use().
the method has():
https://github.com/ZingPHP/Zing/blob/master/Zing/src/Modules/Database/DBOTable.php#L311
EDIT
I think in my callback function I need to do something like this, but I don't think it is working:
public function myFunc(callback $callback){
$callback->bindTo($this, $this);
return call_user_func_array($callback, array());
}
You can go through the public interface of ClassA.
class ClassA
{
public function doCallback($callback)
{
call_user_func_array($callback, array());
}
public function doSomething()
{
echo "Doing something...\n";
}
}
class ClassB
{
private $_oSubject;
public function __construct(ClassA $oSubject)
{
$this->_oSubject = $oSubject;
}
public function runMe()
{
$this->_oSubject->doCallback(function() {
$this->_oSubject->doSomething();
});
}
}
$oA = new ClassA();
$oB = new ClassB($oA);
$oB->runMe();
I got it! I just need to add $newCallback = $callback->bindTo($this, $this); in the callback class, and then in ClassB I can use $this to refer to ClassA.
class ClassA{
public function doCallback($callback){
$callback = $callback->bindTo($this, $this);
call_user_func_array($callback, array());
}
public function doSomething(){
return 12345;
}
}
class ClassB{
public function runMe(){
$classA = new ClassA();
$classA->doCallback(function(){
$this->doSomething();
});
}
}
Im trying to create a twig extension
$loader = new \Twig_Loader_Filesystem(__DIR__.'/../views');
$this->layout = new \Twig_Environment($loader, array(
'cache' => '/../views/cache',
'auto_reload' => true
));
$this->layout->addExtension(new \App\Lib\twig_microtime());
And App\Lib\twig_microtime
class Twig_microtime extends \Twig_Extension {
private $start;
public function getFunctions() {
return array(
'microtime_start' => new \Twig_SimpleFilter($this, 'microtimeStart'),
'microtime_end' => new \Twig_SimpleFilter($this, 'microtimeEnd')
);
}
public function microtimeStart() {
$this->start = microtime(true);
}
public function microtimeEnd() {
return 'eeeee';
}
public function getName() {
return 'microtime_extension';
}
}
So at my layout Im trying to call {{ microtime_end() }} but im getting this error
An exception has been thrown during the compilation of a template ("Argument 2 passed to Twig_NodeVisitor_SafeAnalysis::setSafe() must be of the type array, null given
First you define Filters in the getFunctions method, if these are Filters define them in the getFilters method.
Then the Twig_SimpleFilter and Twig_SimpleFunction object expects an array as the 2nd argument.
So try this:
public function getFilters() {
return array(
new \Twig_SimpleFilter('microtime_start', array($this, 'microtimeStart')),
new \Twig_SimpleFilter('microtime_end', array($this, 'microtimeEnd'))
);
}
But i guess you actually mean to create Functions.
This would be so:
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('microtime_start', array($this, 'microtimeStart')),
new \Twig_SimpleFunction('microtime_end', array($this, 'microtimeEnd'))
);
}
I read about some of the best practices for ZF2. There, it was explained to attach the events from MVC in the init()-Method of the module's Module class:
class Module {
public function getAutoloaderConfig() {
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
);
}
public function init(ModuleManager $moduleManager) {
echo 'init<br>';
$em = $moduleManager->getEventManager();
$em->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'));
$em->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute'));
}
public function onDispatch(MvcEvent $e){
echo 'onDispatch<br>';
}
...
It results in getting no error, nice. But the event is not caught...
Any ideas? I tried the SharedManager too, but it only worked for the EVENT_DISPATCH ...
Unless for specific cases, it's better to register your events in onBootstrap.
init is for "early events".
I found a link that is quite clear : http://samsonasik.wordpress.com/2013/03/30/zend-framework-2-getting-closer-with-eventmanager/
You can find the order of defaults MVC events in Zend\ModuleManager\Listener\DefaultListenerAggregate::attch :
public function attach(EventManagerInterface $events)
{
$options = $this->getOptions();
$configListener = $this->getConfigListener();
$locatorRegistrationListener = new LocatorRegistrationListener($options);
// High priority, we assume module autoloading (for FooNamespace\Module classes) should be available before anything else
$this->listeners[] = $events->attach(new ModuleLoaderListener($options));
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, new ModuleResolverListener);
// High priority, because most other loadModule listeners will assume the module's classes are available via autoloading
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new AutoloaderListener($options), 9000);
if ($options->getCheckDependencies()) {
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new ModuleDependencyCheckerListener, 8000);
}
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new InitTrigger($options));
$this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new OnBootstrapListener($options));
$this->listeners[] = $events->attach($locatorRegistrationListener);
$this->listeners[] = $events->attach($configListener);
return $this;
}
You'll be better using the shared manager. The example bellow is for disabling layout when we got a xmlHttpRequest and the priority -95 is a key point to make things work.
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
// Hybrid view for ajax calls (disable layout for xmlHttpRequests)
$eventManager->getSharedManager()->attach('Zend\Mvc\Controller\AbstractController', MvcEvent::EVENT_DISPATCH, function(MvcEvent $event){
/**
* #var Request $request
*/
$request = $event->getRequest();
$viewModel = $event->getResult();
if($request->isXmlHttpRequest()) {
$viewModel->setTerminal(true);
}
return $viewModel;
}, -95);
}
See http://akrabat.com/zend-framework-2/module-specific-bootstrapping-in-zf2/
Only found this solution: attach listeners in onBootstrap()-Method in the Module.php class:
...
public function onBootstrap(MvcEvent $e){
echo 'onBootstrap<br>';
$em = $e->getApplication()->getEventManager();
$em->attach(MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'));
$em->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute'));
}
...
I have found a bunch of examples how to unit test Zend_Controller, but I'm looking for examples on Zend_Rest_Controller Unit Testing. Any help is really appreciated. Thank you!
So, basically your question is how to emulate calling PUT and DELETE in your controller tests?
Since this apparently doesn't work:
$this->request->setMethod('PUT');
You can access both these actions with plain HTTP POST by providing _method parameter.
So to call PUT:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=put');
To call DELETE:
$this->request->setMethod('POST');
$this->dispatch('articles/123?_method=delete');
More reading on how to deal with RESTful routing here - http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.rest
/**
* Sample class to test a controller
*/
class ArticleControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public $bootstrap;
public function setUp()
{
// When bootstrap is called it will run function 'appBootstrap'
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
public function appBootstrap()
{
$this->application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini');
$this->application->bootstrap();
$bootstrap = $this->application->getBootstrap();
$front = $bootstrap->getResource('FrontController');
$front->setParam('bootstrap', $bootstrap);
}
public function tearDown()
{
Zend_Controller_Front::getInstance()->resetInstance();
$this->resetRequest();
$this->resetResponse();
parent::tearDown();
}
public function testIndexAction()
{
$testCases = array(
'/article/',
'/article/id/123/',
'/article/authorId/777/limit/5/',
'/article/commentId/999/startDate/2011-06-01/endDate/2011-06-01/',
);
foreach ($testCases as $url) {
$this->request->setHeader('Content-Type', 'text/json');
$this->dispatch($url);
$this->assertResponseCode(200);
$this->assertModule('default');
$this->assertController('article');
$this->assertAction('get');
$body = json_decode($this->response->getBody(), true);
$this->assertNotEmpty($body);
...
$this->resetRequest();
$this->resetResponse();
}
}
public function testGetAction()
{
// Same as $this->testIndexAction()
}
public function testPostAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('POST'); before dispatch
// Change $this->assertResponseCode(200); to 201 as REST requires
}
public function testPutAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('PUT'); before dispatch
}
public function testDeleteAction()
{
// Similar to $this->testIndexAction()
// Add $this->request->setMethod('DELETE'); before dispatch
}
}