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',
);
}
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?
i get it
I get it. This only applies to standard CRUD actions created by Gii.
Good article! =)
@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
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
Actually
Actually its working perfectly, I was just calling it too soon (after __construct)
Thanks for the manual
Cheers
Gustavo
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', ), ), ) }
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
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
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', ); } }
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".
@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?
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".
@globaleyeglasses
You do not have: 'actionCartsession', the following statement is wrong-
$this->actionCartsession...
Hello Antonio Ramirez's,
I have a question in yii2.
I rendered a gridview with few information and I applied filters on it.
Those filters are working only when I disabled the pretty urls.
Is there a way to make them work with pretty urls enabled? or it is not possible?
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.