Yii 1.1: Actions code reuse with CAction

24 followers

Introduction

We all know how good 'gii' automates the code for us and we normally tend to be happy with what that tool offers at the beginning of our Yii learning curve. But as soon as you start working in larger and larger projects, you realize that its code is too repetitive to maintain and having a small pitfall in general actions means to go over and over through them to fix the issues.

CAction to the Rescue

I have already explained how to use widgets as action providers to encapsulate the actions. What I am going to explain here is how can we easily create an action to work throughout different controllers.

Gii provides us normally with the following code on the 'actionCreate':

public function actionCreate()
{
   $model=new ModelName;
 
   // Uncomment the following line if AJAX validation is needed
   // $this->performAjaxValidation($model);
 
   if(isset($_POST['ModelName']))
   {
       $model->attributes=$_POST['ModelName'];
       if($model->save())
         $this->redirect(array('view','id'=>$model->id));
   }
 
   $this->render('create',array(
       'model'=>$model,
   ));
}

For a normal project and with the default CMS layout of Yii, does suit our regular needs and we tend to leave it as it is. But, as I said before, imagine that we need to include a new parameter in our redirection for example? In order to avoid that we can tweak a bit the code and develop a general action.

Step 1 - Creating the Action

For the sake of the example, create the following action and save it on your protected/components/actions folder

class Create extends CAction {
 
    public function run() {
    $controller = $this->getController();
 
    // get the Model Name
    $model_class = ucfirst($controller->getId());
 
    // create the Model
    $model = new $model_class();
 
    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);
    if (isset($_POST[$model_class])) {
        $model->attributes = $_POST[$model_class];
 
        if ($model->save())
        $controller->redirect(array('view', 'id' => $model->id));
    }
    $controller->render('create', array(
        'model' => $model,
    ));
    }
 
}

Step 2 - Declare the action on the Controller

Once we have the action class created, the only thing we need to do is declare it in our controller's actions function in order to use it.

public function actions(){
   return array(
      'create'=>'application.components.actions.create',
   );
}

After declaring the action we can call it: http://myhost/index.php?r=controller/create, just like any other.

Final Notes

In the example above I have used 'getController()' and 'getId()' in order to access the model, but we can actually use properties as CAction is a class. This could be the declaration of an action passing the model name to load:

// Assuming the action class has the 
// following public properties:
// public model_name
// -----------------
// ModelClass is a test model class name
// -----------------
// On the controller: 
public function actions(){
   return array(
      'create'=>array(
          'class'=>'application.components.actions.create',
          'model_name'=>'ModelClass',
   );
}

Total 13 comments

#12997 report it
Antonio Ramirez at 2013/04/27 04:35am
@globaleyeglasses

You do not have: 'actionCartsession', the following statement is wrong-

$this->actionCartsession...
#12461 report it
globaleyeglasses at 2013/03/22 08:23am
That worked, but issue with returning value:

@Müller

Thank you, that worked.

But still this does not:

file: components/actions/Cartsession.php
<?php
 
class Cartsession extends CAction {
 
    public function run() {
        if(!Yii::app()->user->isGuest)
        {
            $cart=Cart::model()->findByAttributes(array('customer_id'=>Yii::app()->user->id));
        }
        if(empty($cart))
        {
            $cart=Cart::model()->findByAttributes(array('session_id'=>CHttpSession::getSessionID()));
            if($cart===null)
            {
                $cart=new Cart;
                $cart->customer_id=Yii::app()->user->id;
                $cart->session_id=CHttpSession::getSessionID();
                $cart->save();
                return $cart;
            }
            else
            {
                return $cart;
            }
        }
        else
        {
            return $cart;
        }
    }
 
}
?>
 
in controller:
 
 public function actions(){
            return array(
               'hello'=>'application.components.actions.Hello',
               'cartsession'=>'application.components.actions.Cartsession',
            );
         }
 
public function actionView()
{
$cart=$this->actionCartsession();
                print_r($cart);
                exit();
}

When I run the view function I get the same error: ProductController and its behaviors do not have a method or closure named "actionCartsession".

#12460 report it
Müller at 2013/03/22 07:51am
@globaleyeglasses

@globaleyeglasses

Instead of:

public function actions(){
    return array(
        'create'=>'application.components.actions.Hello',
    );
}

You should write:

public function actions(){
    return array(
        'hello'=>'application.components.actions.Hello',
    );
}

See the difference?

#12459 report it
globaleyeglasses at 2013/03/22 06:36am
CAction does not work?
public function actions(){
            return array(
               'create'=>'application.components.actions.Hello',
            );
         }
 
public function accessRules()
    {
        return array(
            array('allow',  // allow all users to perform 'index' and 'view' actions
                'actions'=>array('index','view','new','resized','hello'),
                'users'=>array('*'),
            ),
            array('allow', // allow authenticated user to perform 'create' and 'update' actions
                'actions'=>array('create','update'),
                'users'=>array('@'),
            ),
            array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'actions'=>array('admin','delete'),
                'users'=>array('admin'),
            ),
            array('deny',  // deny all users
                'users'=>array('*'),
            ),
        );
    }
protected/components/actions/Hello.php
<?php
 
class Hello extends CAction {
 
    public function run() {
    $controller = $this->getController();
 
    // get the Model Name
    $model_class = ucfirst($controller->getId());
 
    // create the Model
    $model = new $model_class();
 
    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);
    if (isset($_POST[$model_class])) {
        $model->attributes = $_POST[$model_class];
 
        if ($model->save())
        $controller->redirect(array('view', 'id' => $model->id));
    }
    $controller->render('create', array(
        'model' => $model,
    ));
    }
 
}
?>

When I run the action it gives a error: http://domain.com/product/hello The system is unable to find the requested action "hello".

#10438 report it
Roman Solomatin at 2012/10/28 02:30pm
Re: How to handle different method implementations

@waterloomatt

You need to create separate action classes for all the custom implementations that you need to use.

For example, your standard create action could be implemented in CreateAction class, and the custom upload implementation in CreateUploadAction class. And then you declare these actions where you need them:

class UserController extends Controller {
  public function actions() {
    return array(
      'create'=>array(
        'class'=>'CreateAction',
     );
  }
}
class ProjectController extends Controller {
  public function actions() {
    return array(
      'create'=>array(
        'class'=>'CreateUploadAction',
     );
  }
}
#5122 report it
waterloomatt at 2011/09/16 02:48am
How to handle different method implementations

Hi,

Good article. Can you explain how to use this for methods that have custom implementations?

Fake scenario: user/actionCreate is standard (generated by Gii) but project/actionCreate needs to upload a file. The file is not mass assigned i.e. not marked as safe in the model.

Am I missing something or are general actions only useful if the code is duplicated exactly - through mass assignment.

Cheers,

Matt

#5069 report it
ManInTheBox at 2011/09/12 03:05pm
Little tune

I rather prefer action class named CreateAction rather than Create... it's more obvious purpose of that class. Also you can put actions in controllers/controllerName directory. Read definitive guide for more information: http://www.yiiframework.com/doc/guide/1.1/en/basics.controller#action

#4096 report it
Attilio at 2011/06/05 10:35pm
Widget + action provider

Small addition to "Final notes" paragraph.

In case you are building a "Widget + action provider" (see Using a Widget as action provider ) passing parameters can be made in this way: (see CController API documentation )

public function actions()
{
    return array(
        'providerName.'=>array(
            'class'=>'path.to.ProviderClass',
            'action1'=>array(
                'property1'=>'value1',
            ),
            'action2'=>array(
                'property2'=>'value2',
            ),
        ),
    )
}
#3786 report it
Gustavo at 2011/05/08 05:13am
Actually

Actually its working perfectly, I was just calling it too soon (after __construct) Thanks for the manual

Cheers

Gustavo

#3785 report it
Gustavo at 2011/05/08 04:57am
bug

Great article There seens to be a bug in version 1.1.7, I cant assign variables to the action class like you described in "Final Notes", which makes it much less powerful

#3365 report it
Antonio Ramirez at 2011/04/06 12:13pm
@rudiedirkx

This article not only applies to general standard CRUD actions, you can actually create any action with this method as long as it is an action that is repeated along your application.

For example, you may have an action display that is exact on every class. As you can see on the article, you render the controller's view.

Cheers

#3364 report it
Dudie Rirkx at 2011/04/06 12:08pm
i get it

I get it. This only applies to standard CRUD actions created by Gii.

Good article! =)

#3363 report it
Dudie Rirkx at 2011/04/06 11:54am
new action (class)

So why not just change the action method actionCreate? Now you created a new class (never good) for a single action that always overwrites the existing one... Did I get that right? Why not just change the existing one?

Leave a comment

Please to leave your comment.

Write new article