How to get controller from within controller plugin in zendframework 2? - php

I am writing a controller plugin in zf2.
I use the following method to get controller from within plugin, but it returns null.
$controller = $this->getController()
Any suggestion?

Note, this answer was based on my experience with ZF1, and a quick look at the ZF2 code. Check out this answer.
I haven't played with ZF2 yet, but if the dispatch process and plugins are similar to ZF1, a plugin can't access the controller (at least not in a trivial way) as the controller isn't even instantiated for some of the plugin hooks.
Update: Took a quick look at some of the stock ZF2 controller plugins (as I can't seem to find official docs on creating a custom plugin), and see checks like the following:
$controller = $this->getController();
if (!$controller || !method_exists($controller, 'plugin')) {
//...
So it seems like the controller may not be set in some cases. Since the plugins also support (what I understand to be) an event listener, my guess is that they still can be used at various times in the response process, which may be before a controller is assigned.
Hopefully someone who's used ZF2 can come along and set me straight; but perhaps I've at least pointed you in a somewhat reasonable direction.

There are two options for which you have no controller set in your plugin.
You call the plugin from the plugin manager prior to dispatch, so no controller is set yet
You call the controller inside the plugin during __construct()
For the first one, a typical example is an onBootstrap() method in a module class where obviously you have no controller:
public function onBootstrap($e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$plugins = $sm->get('ControllerPluginManager');
$plugin = $plugins->get('my-plugin');
// $plugin->getController() === null
}
This seems an obvious example, but there are other occasions where you are mistakenly assuming a controller exists already (for example, during run of the application, at the route phase; the dispatch still has to come).
The second example is because the controller is injected with setter injection. The setter is called after construction. In pseudo code, this happens:
$plugin = new $class;
$plugin->setController($controller);
If you have a plugin like this:
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class MyPlugin extends AbstractPlugin
{
public function __construct()
{
// $this->getController() === null
}
}
You notice there is no controller set at that phase.

Related

yii2 - can someone explain the meaning of parent::init(); statement

I have looked online for the meaning of parent::init(); . All I was able to find was that init() is to initialize some settings which want to be present every time the application runs.
Can anyone please explain the meaning of parent::init() in exact sense, like significance of both the words?
Thanks in advance.( I am sorry if its too simple! )
When we use parent::init(), we are just calling the parent method (in this case init()) inside a method of the current class.
About parent::
For example, let's say we have a class called MyClass. This class have a awesome method that runs alot of things:
class MyClass
{
public function runStuffs()
{
// trigger events, configure external stuff, adding default values to properties.
}
}
Now, after some time, we decided to create a new Class that extends from the first one. And we called MySecondClass:
class MySecondClass extends MyClass
{
}
It already have the method runStuffs(), but, for this second class, we need to do more things in this method, but maintaining what it have.
Sure we could rewrite the whole method and just copy and paste what we have in MyClass and add the new content. But this isn't elegant or even a good practice. what if later on We change the method in MyClass, you probably would like that MysecondClass have that changes too.
So, to solve that problem, we can call the parent method before write your new content:
class MySecondClass extends MyClass
{
public function runStuffs()
{
parent::runStuffs();
// do more things!
}
}
Now MySecondClass->runStuffs() will always do what its parent do and, after that, more stuff.
About the init() method.
init() is a method used in almost all classes from Yii2 framework (since most of then extends from yii\base\Object at some point) and works just like the __constructor() method (native from PHP). But there is some differences, you can read more here.
Actually the init() method is called inside the __constructor(), and the framework encorage us to use init() instead of __construct() whenever is possible.
Now if both are pretty much the same thing, why do they create this method? There is an answer for that here. (take a look at qiang's answer, from the dev team):
One of the reasons for init() is about life cycles of an object (or a component to be exact).
With an init() method, it is possible to configure an object after it is instantiated while before fully initialized. For example, an application component could be configured using app config. If you override its init() method, you will be sure that the configuration is applied and you can safely to check if everything is ready. Similar thing happens to a widget and other configurable components.
Even if init() is called within constructor rather than by another object, it has meaning. For example, in CApplication, there are preInit() and init(). They set up the life cycles of an application and may be overridden so that the customization only occurs at expected life cycles.
Conclusion
So, when you use a init() method and calls parent::init() you are just saying you want to add more things to that method without removing what it already was doing.
The parent::init(); Method is useful to execute a code before every controller and action,
With an init() method, it is possible to configure an object after it is instantiated while before fully initialized.
For example, an application component could be configured using app config.
If you override its init() method, you will be sure that the configuration is applied and you can safely to check if everything is ready.
Similar thing happens to a widget and other configurable components.
In Yii, init() method means that an object is already fully configured and some additional initialization work should be done in this method.
For More Information check this link :
https://stackoverflow.com/questions/27180059/execute-my-code-before-any-action-of-any-controller
Execute my code before any action of any controller
might be helpful to you.

Using a model inside a helper

I've a quick question related to the software architecure. In my application I have a model which contains a method to check the environment the application works in. Let's say the model is called "AppModel".
So, the AppModel::isDevEnv() indicates whether the app is runnig in production or development. It's easy to call this method inside others models, components etc.
The problem is when I want to check the environement inside a view. I created a helper with a propriety method inside just to cover the method from the model and return the result coming from exactly model's method.
class AppModel {
public function isDevEnv() {
return boolean;
}
}
class AppHelper {
public static function isDevEnv() {
$app = new AppModel();
return $app->isDevEnv();
}
}
Is it correct approach? Maybe it's a little bit overcomplicated? Maybe I should just make a static method inside a model and call it whenever I would like to call it?
If this is a legacy system I would recommend to refactor it to the desirable solution. If you want to have this helper or it is a required step for further refactoring then do it.
In general I would inject services which behave differently based on the environment instead of checking the environment inside your models. But it might not be easy with legacy system.

Cakephp custom folder structure for admin

Since my app is getting bigger i would like to separate admin prefix actions and views from normal actions and views. The new folder for admin is Controller/admin/UsersController.php.
I would like to change my cakephp controllers and views folder structure to match the prefix I'm using.
Example for admin prefix:
Controller:
app/Controller/UsersController.php (contain view(), index() ...)
app/Controller/admin/UsersController.php (contain admin_view(), admin_index() ...)
View:
app/View/Users/index.ctp (for index() in UsersController.php)
app/View/Users/admin/index.ctp (for admin_index() in admin/UsersController.php)
How can I implement this structure using Cakephp 2.6?
Unlike in 3.x where this is the default behavior for prefixes, this isn't supported in 2.x.
You could try hacking it in using a custom/extended dispatcher (in order to retrieve the desired controller) or even dispatcher filters in case you are adventurous, and in your app controller modify the view template path with respect to the prefix.
That should do it, however I would probably simply go for using plugins instead, this will separate things just fine without any additional fiddling.
If you just want to separate logic you could do something like this. It's untested an just thought to give you just the idea. I'll explain the concept after the code:
public function beforeFilter() {
if ($this->request->prefix === 'foo') {
$name = Inflector::classify($this->request->prefix);
$className = $name . 'ChildController';
App::uses($className, 'Controller/Foo');
$this->ChildController = new $className($this);
}
}
public function __call($method, $args) {
if ($this->request->prefix === 'foo' && method_exists($this->ChildController, $method)) {
call_user_func_array([$this->ChildController, $method], $args);
}
}
Depending on the prefix you can load other classes. How you load that class and how you instantiate it, what params you pass to it is up to you. In my example I'm passing the controller instance directly. I think you could actually init a complete controller here but be aware that components like the Session might cause a problem because they might have been already initiated by the "parent" controller.
When you now call a controller method that doesn't exist it will try to call the same method with the same arguments on the ChildController. Not really a great name for it, but maybe you can come up with something better.
You'll have to implement some logic to load the views from the right place in your classes as well but this shouldn't be hard, just check the controller class.
But actually I don't see your problem, I've worked on an app that hat over 560 tables and not putting the code into sub folders wasn't a problem, it did in fact use a similar solution.
I think you have to much code in your controllers, get more code into your models and the controller shouldn't be a problem.
Another solution might be to think about implementing a service layer in CakePHP which implements the actual business logic while a model is reduced to a data handler. The service would sit between a controller and the model. I've done this a few times as well now and if done right it works very well.

How to make Zend automatically switch view and layout with contexts?

I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.
Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin
ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.
I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png

Why do I need the Init function in my Controller?

I am new to PHP and I have a few questions that follows:
Do I need the init function or can I do the job (whatever I need to do in my code) without the init function?
I am saying this because the NetBeans "kinda" created/added automatically the init() function in my project.
In my code I am suppose to create the CRUD functionality in it.
If I don't use it what's the problems I might have and the downsides?
As the official docs would say:
The init() method is primarily intended for extending the constructor. Typically, your constructor should simply set object state, and not perform much logic. This might include initializing resources used in the controller (such as models, configuration objects, etc.), or assigning values retrieved from the front controller, bootstrap, or a registry.
You can have controllers that don't override the init() method, but it will be called under the sheets anyways.
If you are new to PHP, do not start by using a framework. Instead you should learn the language itself.
There is nothing significant about init() function. It is not a requirement for classes in PHP. Hell .. even __construct() is not mandatory in PHP.
That said, Zend Framework executes it right after the controller is created. It is required if you are using ZF.
You can read more about it here.
init() in Zend_Framework for most practical purposes is where you would put code that you need to affect all of the actions in that controller.(at least to test against all of the actions).
For example I often use the init() method to set up the the flashmessenger helper and to set the session namespace I want to be used.:
public function init() {
if ($this->_helper->FlashMessenger->hasMessages()) {
$this->view->messages = $this->_helper->FlashMessenger->getMessages();
}
//set the session namespace to property for easier access
$this->_session = new Zend_Session_Namespace('location');
}
Also Netbeans did not make this method or the controller, Zend_Tool made the controller and the methods utilizing the interface that Netbeans provided. That's why in your PHP settings for Netbeans you have to provide the path to the ZF.bat file and click the register provider button if you change your ZF install.
One more thing...Be aware that there more methods available to the controller that provide hooks into different parts of the dispatch cycle. You may not need them very often but you need to know they are there.
Simply its a constructor for that class(controller)...
init(){
$this->a = 1; //If we set something like this in the init
}
public function fooAction(){
echo $this->a; //1
}
public function barAction(){
echo $this->a; //1
}
ie the variables,objects..that is initialised in init will be available to all the actions in that controller

Categories