Calling actions of another controller

Let’s once again discuss the topic which seems to be quite popular here. :) Calling actions of one controler inside another contoller.

Example web portal:

  • Home page

  • Some static pages

  • Forum: nested forums list, threads list, posts list, admin interface etc.

  • Submissions by users: submissions grouped by users, latest submissions, highlights etc.

  • Journals: journals of a user, latest journals etc.

  • User management: register, login, admin interface

  • etc.

Goal: Home page must include: static info, latest submissions, latest journals, latest forum posts. User page must include info about the user, his latest submissions and journals.

How do I do it?

The most simple answer is that I’d like to include on my “user/view?name=username” page this stuff: “forum/posts?sort=date&user=username”, “journals/view?sort=date&user=username” etc. So that UserController.actionView method called a few renderPartials and then render primary view. But… I can’t do that.

  1. The most popular suggestion seems to be to use widgets. I’m newbie, I haven’t written that much code using Yii, but widgets look like rather small pieces with one contoller, single action, usually one view. They’re like controls. Widget is what often called a “control”.

I can’t make forum a widget, forum would too heavy concept for a widget, wouldn’t it? I would also lose all the nice stuff with url management, actions and other goodies from Yii.

  1. Another popular suggestion is to put everything necessary into one controller, to use very fat models so that code in controllers is almost unnecessary.

The problem is, I don’t want to touch too much from forum classes in my user classes. Instead of input parameters (controller-action pair, its arguments) I’ll need to know about models, about model’s methods and properties, about view etc. Even if model is really fat and I just need to call a few methods, that would still be copy-and-paste, that would be unnecessary coupling. Whenever I change ForumController.actionPosts I’ll need to remember to modify UserController.actionView too and vice versa. If I need to refactor forum classes, I’ll need to refactor UserController too. Considering how bad refactoring tools for PHP are (compared to C#, Java etc.), it would be much harder.

  1. Heard another suggestion too. To use AJAX and to load content dynamically. Absolutely not an option. This solution makes indexing by search engines impossible. What’s the point of having a site with content which nobody can find?

4a. Hacky solution. To call CWebApplication.createController, runController. This way, we can use controller and its actions without worrying about their internals. However, we lose possibility to pass arguments. Both CUrlManager and $_GET will contain information for parent page, so we’ll have to find another way to pass arguments. And to tell that we only need partial renders too.

4b. Variation of the prevous hacky solution. Modify $_GET and make CUrlManager parse URL again. Now, we’ve got controllers, actions and arguments. It finally works! But… I feel dirty.

What is the true way?

I’m not sure if I got the point. If you put everything into one controller, then it’s fat controller not fat model. Usually I put all logic into model (easier to design/test and maintain). You can write some static functions like: public static CActiveRecord getLastPosts($count = 10, $user = null), public static CActiveRecord getLastTopics($count = 10, $user = null) and so on. You will use these functions in all widgets and controllers so I don’t see any place for copy & paste. If last topics will be displayed only in one controller different from forum controller then create view and render it partial when desired. Otherwise, go for a widget (remember that widget can take parameters so you can reuse it on many pages by just changing one or two variables). If you need to change any logic, then all you need to change is forum model.

I found the answer in CController::forward().