calling other controller actions

Hi.

I needed to call another controller’s action and that seemed to be a problem using yii. I heard that this ability is somehow linked with hmvc (don’t know how though).

So I wrote a little function which implements this functionality and placed it in the Controller class:




private $_action;


public function useAction($route,$controller=NULL)

{

	ob_start();


	if(empty($controller)){

		// ## this is almost CWebApp::runController

		if(($ca=Yii::app()->createController($route))!==null){

			list($controller,$actionID)=$ca;

			$controller -> init();

			$controller -> _action = $actionID;

			$controller -> run($actionID);

			unset($controller);

		}else{

			throw new Exception('Dont know this controller: '.$route);

		}

	}else{

		$oldaction = $controller -> _action;

		$controller -> _action = $actionID;

		$controller -> run($route);

		$controller -> _action = $oldaction;

	}

		

	return ob_get_clean();

}



Is this acceptable? Is it right?

Your solution looks more like a “hack” to me. And I don’t like hacks :)

There are more "clear" ways to do it, e.g. create an action class, use CController.forward() method, create a base class for controllers, or simply redirect.

If none of this ways suits you, then there is probably a design mistake (IMHO of course).

Thanks for sharing

Some obsevations:

-your get variable would be the same in the controller you are calling, and that may cause unexpected behaviors

  • its not using curlmanager

to fix you could do something like:




public static function runModule($uri,$args=array()){

    	$olduri=$_SERVER['REQUEST_URI'];

 		//clean up the get variable and save the old content

    	$oldget=$_GET;

    	$_GET=array();

    	//parse the url using url manager

    	$_SERVER['REQUEST_URI']="/".$uri;

    	$urlManager=Yii::app()->getUrlManager();

    	$url=$urlManager->parseUrl(new CHttpRequest());

    	//set the get variables

    	if(is_array($args) && count($args)>0){

        	foreach($args as $k=>$v)

            	@$get="{$k}/{$v}/";

        	$urlManager->parsePathInfo(trim($get,"/"));

    	}

    	

        	ob_start();

        	ob_implicit_flush(false);

        	Yii::app()->runController($url)

        	$_GET=$oldget;

        	$_SERVER['REQUEST_URI']=$olduri;

   		return ob_get_clean();

	}



-the right place to post would be under tips , snippets & tutorials

anyway, you are not necessarily wrong doing something like that ( zend has a built-in method to do it )

andy_s

Redirecting is not it. The Controller::forward method is almost redirecting. Don’t see how creating an action class would help.

And yes, I surely placed this function in Controller base class (the one that is automatically created with application in the components directory).

Gustavo

Thanks! I would place it under tips but I don’t have enough rating. Also I’d prefer first to discuss it.

Didn’t think about CUrlManager and $_GET at all. That’s very intresting.

Abstract

The reason I needed this function is to build a page using several actions. Each action displays a part of the page and sometimes I needed to call actions from external Controllers. Also I was building something like a CMS where needed actions where called depending on the data in DB. Layouts are just not enough.

Also I am confused about possible creation of one controller several times. I suppose there should be a private array of all created controllers and before creating another one we should check if he exists.

Then why use MVC at all?

Why not widgets? :)

@jacmoe sometimes a widget is not enough cuz you might need an whole module/controller for some content

This script is good for caching also, something like:




private static $cache=100;//or false

//...

   	$token='uri.'.$url.@$get;

    	if(self::$cache!==false && !($content=Yii::app()->getCache()->get($token))){

        	ob_start();

        	ob_implicit_flush(false);

        	Yii::app()->runController($url);

        	$content=ob_get_clean();

        	Yii::app()->getCache()->set($token,$content,self::$cache);

    	}



but you can only it if you are sure that your client has permissions to access that content

or you could implement in the function some kind of verification

jacmoe

And why not?

I thought of creating whole app so that one would be able to display any logical part of the page anywhere else.

Widgets allows you to show whatever you want wherever you want.

Your approach is very clever, maybe a bit too clever. IMO.

Do you need more than one single controller?

Maybe not.

If you do, then you could create an application component if you want to have overall logic.

What you are doing now just looks really dirty, and breaks rules of encapsulation.

But, I have to give you that: it’s dirty in a rather neat way. :)

Also, if you follow the fat models, lean controllers rule, I don’t think you need to be able to call controller functions from another controller.

Especially paired with judicious use of widgets.

I don’t agree much. :)

If you keep with the fat models, lean controllers rule, then just call the model code from any controller, and use intelligent widgets for the presentation.

You could couple that with components, and get an even more flexible solution.

Without all the coupling.

Whole page is not a "logical part". An action renders a view with a layout. Or you will use renderPartial() in each action? A logical part which you can share between different pages = widget.

an other solution would be to ajax load your content, which is lighter and a more correct approach in terms of standards & padrons

Ok, maybe a widget it should be.

In my little cms I stored info about which logical part should a page have in DB and created a AR class (I called it a Block) which called actions from different controllers. Each Block represented some action specially marked in phpdoc style.

I suppose that Block is close to the widget here.

Though if I use widgets instead - why would I need controllers then?

The filter thing is pretty nice also.

jacmoe

Why not use only one controller in every app then?

The reason why I started the topic was to find out - what’s so dirty about this stuff?

andy_s

You’re right. While building the page: renderPartial() every time and render() - only once per page.

Gustavo

Speaking of ajax: I heard of ideas even creating javascript views. So that the web server would only return pure data (in json I suppose) and javascript would render it. No need in php then. But this way requires a very strong js framework. Ahh, f man can always dream… :)

Just using ajax is not so hard but why not try building pages the hmvc way?

Model - View - Controller, remember?

It’s up to you to learn and discover how to apply MVC on your project.

The reason why I suggested using widgets is that they are essentially a mini-controller with a view.

And they’re are meant to be small, well-defined and reusable.

The structure of your application will decide how many controllers you need. IMO.

Just think about the urls you need for your application - site/[<module>]controller/action/parameters - and you get a pretty good picture of how many controllers you need.

Grouped by functionality/responsibility.

Sometimes, you only need one controller. Nothing wrong with that. Depends on your use case.

But the main deciding factor on your design is of course your database schema. But that is another topic, really.

So what’s dirty about calling actions of external controllers?

And where is there a break in rules of encapsulation&

I don’t really care. Go ahead. ;)

Just kidding: I care.

Look at controllers as black Lego ™ bricks.

Ideally, you should be able to take one brick and replace it with another, without breaking anything.

If you need common functionality, you could put the common functions in a parent class.

Or, if it’s not tied to a controller, a component.

But mainly: put it in the model. If it’s not worth putting in a model, it’s not worth being shared.

Yet two other approaches:

You can use behaviors (both models and controllers), or filters.

If that’s not enough, you can look into actions (CAction) and action mapping. Maybe even reflection.

Look at the webservice classes in Yii. They are a good example of how to do that: registerable actions with a service manager.

All of those alternatives are far better and a lot cleaner than letting your controllers talk to each other.

You will be able to create a much more scalable code base if you follow those rules.

And lastly:

Do no underestimate the power of widgets.

Food for thought:

http://stackoverflow.com/questions/1296680/net-mvc-call-method-on-different-controller

And a quote:

Look at the code for Phundament II.

Especially the cellmanager (which is a widget) which is responsible for loading and displaying registered ICellManagerWidgets.

Spent some more time designing your architecture, you’ll thank yourself later.

All IMO, of course. :)

<edit>

Forgot the link:

http://code.google.com/p/phundament/

Look in trunk/modules/p2/components/cellmanager first.

Actually I really am redesigning my architecture already. And I needed your advice.

So, thanks a lot, guys! For your response and especially for caring :)

Actions really are "publicly visible HTTP endpoints" - good point.

Controllers don’t talk to each other - got it.

Ok, I’ll have to study your links and think more.

Perhaps CAction will help. When I think of something I suppose I’ll ask again :)

P.S. Wow. So they exist… I tried to google out a cms based on yii but couldn’t :(