PK >K:% crud-5.1.1/contents.html
Crud was built to be scaffolding on steroids, and allow developers to have enough flexibility to use it for both rapid prototyping and production applications, even on the same code base saving you time, and your clients money.
If you happen to stumble upon a bug, please feel free to create a pull request or submit an issue with a fix (optionally with a test), and a description of the bug and how it was resolved.
If you have a good idea for a Crud feature, please join us in #friendsofcake on irc and let’s discuss it. Opening a pull request is always more than welcome, and a great way to start a discussion. Please check our contribution guidelines.
You can join us in #friendsofcake on irc on irc.freenode.net for any support or questions.
The recommended installation method for this plugin is by using composer.
composer require friendsofcake/crud:^4.3
You can also check Packagist.
The Crud plugin provides a trait which will catch a MissingActionException and then step in to provide scaffold actions to the controllers.
To enable Crud across your whole application add the trait to your src/Controller/AppController.php
namespace App\Controller;
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
}
Note
To have Crud just scaffold a single controller you can just add the ControllerTrait
to that specific controller.
Adding the ControllerTrait
itself do not enable anything Crud, but simply installs the code to handle
the \Cake\Error\MissingActionException
exception so you don’t have to implement an action in your controller
for Crud to work.
The next step is to load the Crud component in your controller. A basic example is as follows, and will enable the Crud plugin to scaffold all your controllers index actions.
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index'
]
]);
// Other application wide controller setup
}
}
Further configuration options are detailed on the configuration page.
You are busy, and you just want to “get things done™”, so let’s get going.
After installation, you are ready to CRUD-ify your app.
So the application our pointy-haired boss has tasked us to create today is a Blog.
Let’s setup CRUD to handle all index()
, add()
, edit()
, view()
and delete()
actions automatically,
we do this by enabling Crud in the AppController
with the actions
options configuration.
namespace App\Controller;
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.Add',
'Crud.Edit',
'Crud.View',
'Crud.Delete'
]
]);
}
}
There we go, that was easy.
So, our new Blog needs a Posts controller to allow us to create, read, update and delete posts.
namespace App\Controller;
class PostsController extends AppController
{
}
This is all the code we need in the PostsController
as Crud will scaffold the controller actions for us.
If you are not using Crud-View then you will have to bake your templates.
bin/cake bake template posts
Let’s check out our new application, go to /posts
and behold, a nice paginated ìndex()
template, all without any code
in your controller.
You should now be able to navigate to /posts/add
as well and create your first post, as well as being able to edit it.
If you’d rather look through a completed application, José from the CakePHP core team has created an application using Crud. You can find it on Github.
Configuration of Crud is done through the Crud component - either on the fly
anywhere in you application, or by providing the configuration in the
Controller::loadComponent()
method.
Assuming you have followed the installation guide we will now begin the actual configuration of Crud.
class AppController extends \Cake\Controller\Controller
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud');
}
}
At this time, the Crud Component is loaded, but we need to tell Crud which actions we want it to handle for us.
The list of actions is provided either as Component configuration, or on the fly.
An example configuration for handling index actions looks like this.
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index'
]
]);
}
}
An example of on the fly enabling an Crud action:
class AppController extends \Cake\Controller\Controller
{
public function beforeFilter(\Cake\Event\Event $event)
{
$this->Crud->mapAction('index', 'Crud.Index');
}
}
The examples above are functionally identical, and instructs Crud to handle the
index
action in controllers using Crud.Index
action class.
Note
If you do not wish for Crud to be enabled across all controllers, or even use
all actions
provided by Crud you can pick and chose which to use.
Crud will not force take-over any application logic, and you can enable/disable
them as you see fit.
Note
Each Crud Action can have a different set of configuration settings, please see their individual documentation pages for more information.
A more verbose example now, where we’ll change the view template that Crud will use for index actions to be my_index.ctp
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'index' => [
'className' => 'Crud.Index',
'view' => 'my_index'
]
]
]);
}
}
An example of on the fly enabling a Crud action with configuration
class AppController extends \Cake\Controller\Controller
{
public function beforeFilter(\Cake\Event\Event $event)
{
$this->Crud->mapAction('index', [
'className' => 'Crud.Index',
'view' => 'my_index'
]);
}
}
If you’ve loaded an action in eg. your AppController
- but don’t want it included in a specific controller, it can
be disabled with the $this->Crud->disable(['action_name'])
.
Example of disabling a loaded action, first we show all actions being configured to be handled by Crud, then disabling a
specific action in our PostsController
.
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Delete',
'Crud.Edit'
]
]);
}
}
class PostsController extends AppController
{
public function beforeFilter(\Cake\Event\Event $event)
{
parent::beforeFilter($event);
$this->Crud->disable(['Edit', 'Delete']);
}
}
Crud provides the default create, read, update and delete actions out of the box.
It’s possible to create your own custom action classes as well, or overwrite the built-in ones. Simply provide
the className
configuration key for an action, and Crud will use that one instead.
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'index' => ['className' => '\App\Crud\Action\MyIndexAction'],
'view' => ['className' => '\App\Crud\Action\MyViewAction']
]
]);
}
}
Note
Ensure that you escape your namespace when loading your own action classes.
The other way to customise the behavior of the Crud plugin is through it’s many listeners. These provide lots of additional functionality to your scaffolding, such as dealing with api’s and loading related data.
Check the Listeners documentation for more on Crud’s included listeners, and how to create your own.
You might have a scenario where you’d like to use Crud, but only within a certain prefix, such as running your admin
area on Crud under the admin
prefix.
The easiest way to achieve this is to create an AppController
for the prefix, and have your other prefixed controllers
extend from that one. Then you can configure Crud in your prefixes AppController
.
Let’s look at an example, using an api
prefix. For this example, we’ll assume your
prefix routing is already configured.
First step is to create your new ApiAppController
which should be in src/Controller/Api/
.
namespace App\Controller\Api;
class ApiAppController extends Controller
{
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View'
]
]);
$this->Crud->addListener('Crud.Api');
$this->Crud->addListener('Crud.ApiPagination');
}
}
So now that we’ve created our new ApiAppController
we can extend the other prefix controllers from this one, so that
they inherit the Crud configuration without impacting other areas of our application.
namespace App\Controller\Api;
class ProductsController extends ApiAppController
{
}
Note
CRUD already provides the basic Index
, View
, Add
, Edit
and
Delete
actions, so you do not need to implement these on your own.
You can find the documentation for these actions in the menu to the left.
Crud Actions are the backbone of the plugin, this is where most of the logic happens.
A Crud action roughly translates to a normal Controller action. The primary difference is that CRUD actions are made to be as generic and secure out of the box as possible.
You can consider a CRUD action as a more flexible PHP trait that fits nicely within the CakePHP ecosystem.
A nice added bonus is that if all your controllers share a generic controller action, you only have to unit test once, to test every controller in your application.
Below is the code for the Index Crud Action
In the next few sections we will walk through the code and explain how it works, and what every single line of code does. For each section, the relevant lines of code will be highlighted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace Crud\Action;
class Index extends BaseAction
{
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$subject = $this->_subject();
$subject->set(['success' => true, 'viewVar' => $this->viewVar()]);
$this->_trigger('beforePaginate', $subject);
$controller = $this->_controller();
$items = $controller->paginate();
$subject->set(['items' => $items]);
$this->_trigger('afterPaginate', $subject);
$controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]);
$this->_trigger('beforeRender', $subject);
}
}
|
All build-in actions in Crud live in the Crud\Action
namespace.
All actions in Crud, even your own, should inherit from the
Crud\Action\Base
class.
This class is abstract
and provides numerous auxiliary methods which can be
useful for you both as a developer as an action creator.
Where you choose to put your custom Crud actions is up to you, but using src/Crud
is a good place. Remember to
namespace your action class accordingly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace Crud\Action;
class Index extends BaseAction
{
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$subject = $this->_subject();
$subject->set(['success' => true, 'viewVar' => $this->viewVar()]);
$this->_trigger('beforePaginate', $subject);
$controller = $this->_controller();
$items = $controller->paginate();
$subject->set(['items' => $items]);
$this->_trigger('afterPaginate', $subject);
$controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]);
$this->_trigger('beforeRender', $subject);
}
}
|
Next is the method _handle
. A Crud Action can respond to any HTTP verb
(GET
, POST
, PUT
, DELETE
).
Each HTTP verb can be implemented as method, e.g. _get()
for HTTP GET,
_post()
for HTTP POST and _put()
for HTTP PUT.
If no HTTP verb specific method is found in the class, _handle()
will be
executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace Crud\Action;
class Index extends BaseAction
{
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$subject = $this->_subject();
$subject->set(['success' => true, 'viewVar' => $this->viewVar()]);
$this->_trigger('beforePaginate', $subject);
$controller = $this->_controller();
$items = $controller->paginate();
$subject->set(['items' => $items]);
$this->_trigger('afterPaginate', $subject);
$controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]);
$this->_trigger('beforeRender', $subject);
}
}
|
You can treat the _handle()
method as a catch-all, if your crud action
wants to process all possible HTTP verbs.
An advantage of this setup is that you can separate the logic on a request type level instead of mixing all of the logic into one big block of code.
For example the Edit Crud Action implements _get()
,
_post()
and _put()
methods. The _get()
method simply reads the entity
from the database and passes it to the form, while _put()
handles validation
and saving the entity back to the database.
All Crud actions emit a range of events, and all of these events always contain a Crud Subject. The Crud Subject can
change its state between emitted events. This object is a simple StdClass
which contains the current state of the Crud request.
The real beauty of Crud is the events and the flexibility they provide.
All calls to _trigger()
emit an event, that you as a developer can listen to and inject your own application logic.
These events are in no way magical, they are simply normal
CakePHP events, dispatched like all
other events in CakePHP.
You can for example listen for the beforePaginate
event and add conditions to your pagination query, just with a
few lines of code. Those few lines of code is what makes your application unique. The rest of the code you would
normally have is simply repeated boiler plate code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace Crud\Action;
class Index extends BaseAction
{
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$subject = $this->_subject();
$subject->set(['success' => true, 'viewVar' => $this->viewVar()]);
$this->_trigger('beforePaginate', $subject);
$controller = $this->_controller();
$items = $controller->paginate();
$subject->set(['items' => $items]);
$this->_trigger('afterPaginate', $subject);
$controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]);
$this->_trigger('beforeRender', $subject);
}
}
|
Only the code that you would normally have in your controller is left now.
While these 3 lines seem simple, and the whole Crud implementation a bit overkill at first, the true power of this setup will be clear when your application grows and the requirements increase.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
namespace Crud\Action;
class Index extends BaseAction
{
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$subject = $this->_subject();
$subject->set(['success' => true, 'viewVar' => $this->viewVar()]);
$this->_trigger('beforePaginate', $subject);
$controller = $this->_controller();
$items = $controller->paginate();
$subject->set(['items' => $items]);
$this->_trigger('afterPaginate', $subject);
$controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]);
$this->_trigger('beforeRender', $subject);
}
}
|
For example adding an API layer to your application later in time will be easy because you don’t need to edit all your applications many controllers.
Using Crud, it would be as simple as loading the API listener and everything would be taken care of. All validation, exceptions, success and error responses would work immediately, and with just a few lines of code.
This is because the powerful event system can hook into the request and hijack the rendering easily and effortlessly; something baked controllers do not offer.
Crud has many actions built-in which come with the plugin.
The Index Crud Action
paginates over the primary model in the controller.
On a high level it’s basically just calling Controller::paginate()
.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
Get or set the view file to render at the end of the request.
The view setting is passed directly and unmodified to Controller::render()
.
To get the current configured view
call the view
method without any arguments.
$this->Crud->action()->view();
To change the view to render, pass a string
as first argument.
$this->Crud->action()->view('my_custom_view');
Note
If the first parameter is NULL
- which is the default value - the normal CakePHP behavior will be used.
Warning
Due to the nature of this method, once a custom view has been set, it’s not possible to revert back to
the default behavior by calling ->view(null)
as it will return the current configuration.
Note
This maps directly to the $key
argument in Controller::set($key, $value)
Change the name of the variable which contains the result of a index
or view
action query result.
To get the current configured viewVar
call the viewViar
method without any arguments.
$this->Crud->action()->viewVar();
To change the viewVar, pass a string
as first argument.
$this->Crud->action()->viewVar('items');
For Index Action the default is plural version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be posts
by default.
For View Action the default is singular version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be post
by default.
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the Index Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before Controller::paginate()
is called.
public function index()
{
$this->Crud->on('beforePaginate', function(\Cake\Event\Event $event) {
$this->paginate['conditions']['is_active'] = true;
});
return $this->Crud->execute();
}
This event is emitted right after the call to Controller::paginate()
.
The entities
property of the event object contains all the database records found in the pagination call.
public function index()
{
$this->Crud->on('afterPaginate', function(\Cake\Event\Event $event) {
foreach ($event->getSubject()->entities as $entity) {
// $entity is an entity
}
});
return $this->Crud->execute();
}
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
The View Crud Action
will read a record from a data source based on the ID
that is part of the request.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
Get or set the view file to render at the end of the request.
The view setting is passed directly and unmodified to Controller::render()
.
To get the current configured view
call the view
method without any arguments.
$this->Crud->action()->view();
To change the view to render, pass a string
as first argument.
$this->Crud->action()->view('my_custom_view');
Note
If the first parameter is NULL
- which is the default value - the normal CakePHP behavior will be used.
Warning
Due to the nature of this method, once a custom view has been set, it’s not possible to revert back to
the default behavior by calling ->view(null)
as it will return the current configuration.
Note
This maps directly to the $key
argument in Controller::set($key, $value)
Change the name of the variable which contains the result of a index
or view
action query result.
To get the current configured viewVar
call the viewViar
method without any arguments.
$this->Crud->action()->viewVar();
To change the viewVar, pass a string
as first argument.
$this->Crud->action()->viewVar('items');
For Index Action the default is plural version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be posts
by default.
For View Action the default is singular version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be post
by default.
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the View Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
The event is emitted before calling the find method in the table.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.This is the last place you can modify the query before it’s executed against the database.
Note
An example
Given the URL: /posts/view/10
the repository
object will be an instance of PostsTable
and the query
includes a WHERE
condition with Posts.id = 10
After the event has emitted, the database query is executed with LIMIT 1
.
If a record is found the Crud.afterFind
event is emitted.
Warning
If no record is found in the database, the recordNotFound
event is emitted instead of Crud.afterFind
.
public function delete($id)
{
$this->Crud->on('beforeFind', function(\Cake\Event\Event $event) {
$event->getSubject()->query->where(['author' => $this->Auth->user('id')]);
});
return $this->Crud->execute();
}
After the query has been executed, and a record has been found this event is emitted.
The Crud Subject contains two keys:
id
The ID that was originally passed to the action and is usually the primary key of your model.entity
The record that was found in the database.Note
If an entity is not found, the RecordNotFound
event is emitted instead.
public function delete($id)
{
$this->Crud->on('afterFind', function(\Cake\Event\Event $event) {
$this->log("Found item: " . $event->getSubject()->entity->id . " in the database");
});
return $this->Crud->execute();
}
Note
This event will throw an exception.
The default configuration will thrown an Cake\Error\NotFoundException
which will yield a 404 response.
The event is triggered after a find
did not find any records in the database.
You can modify the exception class thrown using CrudComponent::message
method
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
The Add Crud Action
will create a new record if the request is POST
and the data validates - otherwise it will attempt to render a form to the end-user.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
Get or set the view file to render at the end of the request.
The view setting is passed directly and unmodified to Controller::render()
.
To get the current configured view
call the view
method without any arguments.
$this->Crud->action()->view();
To change the view to render, pass a string
as first argument.
$this->Crud->action()->view('my_custom_view');
Note
If the first parameter is NULL
- which is the default value - the normal CakePHP behavior will be used.
Warning
Due to the nature of this method, once a custom view has been set, it’s not possible to revert back to
the default behavior by calling ->view(null)
as it will return the current configuration.
The method to execute on Table::
when saving an entity - the default value is save
.
To get the current configured saveMethod
keys call the saveMethod
method without any arguments.
$this->Crud->action()->saveMethod();
To change the saveMethod value pass an string argument to the method
$this->Crud->action()->saveMethod('my_custom_save_method');
The 2nd parameter to Table::save()
- the default value is ['validate' => true, 'atomic' => true]
.
To get the current configured saveOptions
keys call the saveOptions
method without any arguments.
$this->Crud->action()->saveOptions();
To change the saveOptions value pass an array argument to the method
$this->Crud->action()->saveOptions(['atomic' => false]);
Sometimes you need to change the accessible fields before you update your entity.
$this->Crud->action()->saveOptions(['accessibleFields' => ['role_id' => true]]);
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the Add Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
Note
Do not confuse this event with the beforeSave
callback in the ORM layer
Called right before calling Table::save()
.
The Crud Subject contains the following keys:
entity
object marshaled with the HTTP POST
data from the request.string
with the saveMethod
.array
with the saveOptions
.All modifications to these keys will be passed into the Table::$saveMethod
.
Warning
After this event has been emitted, changes done through the $action->saveMethod()
or $action->saveOptions()
methods will no longer affect the code, as the rest of the code uses the values from the Crud Subject
Note
Do not confuse this event with the afterSave
callback in the ORM layer.
This event is emitted right after the call to Table::save()
.
The Crud Subject contains the following keys:
Table::save()
was successful.Table::save()
call succeed or not.true
if the record was created
and false
if the record was updated
.entity
object marshaled with the HTTP POST
data from the request and the save()
logic.public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created");
} else {
$this->log("The entity was updated");
}
});
return $this->Crud->execute();
}
public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->success) {
$this->log("The entity was saved successfully");
} else {
$this->log("The entity was NOT saved successfully");
}
});
return $this->Crud->execute();
}
public function add()
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created with id: " . $event->getSubject()->id);
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
The Edit Crud Action
will update an existing record if the request is POST
or PUT
and the data validates - otherwise it will attempt to render a form to the end-user.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
Get or set the view file to render at the end of the request.
The view setting is passed directly and unmodified to Controller::render()
.
To get the current configured view
call the view
method without any arguments.
$this->Crud->action()->view();
To change the view to render, pass a string
as first argument.
$this->Crud->action()->view('my_custom_view');
Note
If the first parameter is NULL
- which is the default value - the normal CakePHP behavior will be used.
Warning
Due to the nature of this method, once a custom view has been set, it’s not possible to revert back to
the default behavior by calling ->view(null)
as it will return the current configuration.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
The method to execute on Table::
when saving an entity - the default value is save
.
To get the current configured saveMethod
keys call the saveMethod
method without any arguments.
$this->Crud->action()->saveMethod();
To change the saveMethod value pass an string argument to the method
$this->Crud->action()->saveMethod('my_custom_save_method');
The 2nd parameter to Table::save()
- the default value is ['validate' => true, 'atomic' => true]
.
To get the current configured saveOptions
keys call the saveOptions
method without any arguments.
$this->Crud->action()->saveOptions();
To change the saveOptions value pass an array argument to the method
$this->Crud->action()->saveOptions(['atomic' => false]);
Sometimes you need to change the accessible fields before you update your entity.
$this->Crud->action()->saveOptions(['accessibleFields' => ['role_id' => true]]);
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the Edit Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
The event is emitted before calling the find method in the table.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.This is the last place you can modify the query before it’s executed against the database.
Note
An example
Given the URL: /posts/view/10
the repository
object will be an instance of PostsTable
and the query
includes a WHERE
condition with Posts.id = 10
After the event has emitted, the database query is executed with LIMIT 1
.
If a record is found the Crud.afterFind
event is emitted.
Warning
If no record is found in the database, the recordNotFound
event is emitted instead of Crud.afterFind
.
public function delete($id)
{
$this->Crud->on('beforeFind', function(\Cake\Event\Event $event) {
$event->getSubject()->query->where(['author' => $this->Auth->user('id')]);
});
return $this->Crud->execute();
}
After the query has been executed, and a record has been found this event is emitted.
The Crud Subject contains two keys:
id
The ID that was originally passed to the action and is usually the primary key of your model.entity
The record that was found in the database.Note
If an entity is not found, the RecordNotFound
event is emitted instead.
public function delete($id)
{
$this->Crud->on('afterFind', function(\Cake\Event\Event $event) {
$this->log("Found item: " . $event->getSubject()->entity->id . " in the database");
});
return $this->Crud->execute();
}
Note
Do not confuse this event with the beforeSave
callback in the ORM layer
Called right before calling Table::save()
.
The Crud Subject contains the following keys:
entity
object marshaled with the HTTP POST
data from the request.string
with the saveMethod
.array
with the saveOptions
.All modifications to these keys will be passed into the Table::$saveMethod
.
Warning
After this event has been emitted, changes done through the $action->saveMethod()
or $action->saveOptions()
methods will no longer affect the code, as the rest of the code uses the values from the Crud Subject
Note
Do not confuse this event with the afterSave
callback in the ORM layer.
This event is emitted right after the call to Table::save()
.
The Crud Subject contains the following keys:
Table::save()
was successful.Table::save()
call succeed or not.true
if the record was created
and false
if the record was updated
.entity
object marshaled with the HTTP POST
data from the request and the save()
logic.public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created");
} else {
$this->log("The entity was updated");
}
});
return $this->Crud->execute();
}
public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->success) {
$this->log("The entity was saved successfully");
} else {
$this->log("The entity was NOT saved successfully");
}
});
return $this->Crud->execute();
}
public function add()
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created with id: " . $event->getSubject()->id);
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
The Delete Crud Action
will delete a record by the id provided in the URL.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the Delete Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
The event is emitted before calling the find method in the table.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.This is the last place you can modify the query before it’s executed against the database.
Note
An example
Given the URL: /posts/view/10
the repository
object will be an instance of PostsTable
and the query
includes a WHERE
condition with Posts.id = 10
After the event has emitted, the database query is executed with LIMIT 1
.
If a record is found the Crud.afterFind
event is emitted.
Warning
If no record is found in the database, the recordNotFound
event is emitted instead of Crud.afterFind
.
public function delete($id)
{
$this->Crud->on('beforeFind', function(\Cake\Event\Event $event) {
$event->getSubject()->query->where(['author' => $this->Auth->user('id')]);
});
return $this->Crud->execute();
}
After the query has been executed, and a record has been found this event is emitted.
The Crud Subject contains two keys:
id
The ID that was originally passed to the action and is usually the primary key of your model.entity
The record that was found in the database.Note
If an entity is not found, the RecordNotFound
event is emitted instead.
public function delete($id)
{
$this->Crud->on('afterFind', function(\Cake\Event\Event $event) {
$this->log("Found item: " . $event->getSubject()->entity->id . " in the database");
});
return $this->Crud->execute();
}
Note
This event will throw an exception.
The default configuration will thrown an Cake\Error\NotFoundException
which will yield a 404 response.
The event is triggered after a find
did not find any records in the database.
You can modify the exception class thrown using CrudComponent::message
method
This event is emitted before calling Table::delete
.
The Crud Subject contains the following keys:
Entity
from the find()
call.To abort a delete()
simply stop the event by calling
$event->stopPropagation()
.
public function delete($id)
{
$this->Crud->on('beforeDelete', function(\Cake\Event\Event $event) {
// Stop the delete event, the entity will not be deleted
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after Table::delete()
has been called.
The Crud Subject contains two keys:
true
the delete()
call succeeded, false
otherwisepublic function delete($id)
{
$this->Crud->on('afterDelete', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Delete failed for entity " . $event->getSubject()->id);
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
The Lookup Crud Action
will display a record from a data source for auto-complete purposes. Used mostly for Crud-View.
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
Get or set the view file to render at the end of the request.
The view setting is passed directly and unmodified to Controller::render()
.
To get the current configured view
call the view
method without any arguments.
$this->Crud->action()->view();
To change the view to render, pass a string
as first argument.
$this->Crud->action()->view('my_custom_view');
Note
If the first parameter is NULL
- which is the default value - the normal CakePHP behavior will be used.
Warning
Due to the nature of this method, once a custom view has been set, it’s not possible to revert back to
the default behavior by calling ->view(null)
as it will return the current configuration.
Note
This maps directly to the $key
argument in Controller::set($key, $value)
Change the name of the variable which contains the result of a index
or view
action query result.
To get the current configured viewVar
call the viewViar
method without any arguments.
$this->Crud->action()->viewVar();
To change the viewVar, pass a string
as first argument.
$this->Crud->action()->viewVar('items');
For Index Action the default is plural version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be posts
by default.
For View Action the default is singular version of the controller name.
Having a controller named PostsController
would mean that the viewVar
would be post
by default.
Note
This setting is only relevant if you use the API listener.
Note
The API listener will always enforce success
and data
to be part of the _serialize
array.
This method is intended to allow you to add additional keys to your API responses with ease. An example of this is the API Query Log.
To get the current configured serialize
keys call the serialize
method without any arguments.
$this->Crud->action()->serialize();
To change the serialize keys, pass a string
or an array
as first argument.
If a string is passed, it will be cast to array
automatically.
$this->Crud->action()->serialize(['my', 'extra', 'keys']);
This is a list of events emitted from the Lookup Crud Action
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before Controller::paginate()
is called inside the Lookup Action.
public function lookup()
{
$this->Crud->on('beforeLookup', function(\Cake\Event\Event $event) {
$this->paginate['conditions']['is_active'] = true;
});
return $this->Crud->execute();
}
This event is emitted right after the call to Controller::paginate()
in the Lookup Action.
The entities
property of the event object contains all the database records found in the pagination call.
public function lookup()
{
$this->Crud->on('afterLookup', function(\Cake\Event\Event $event) {
foreach ($event->getSubject()->entities as $entity) {
// $entity is an entity
}
});
return $this->Crud->execute();
}
Note
This event will throw an exception.
The default configuration will thrown an Cake\Error\NotFoundException
which will yield a 404 response.
The event is triggered after a find
did not find any records in the database.
You can modify the exception class thrown using CrudComponent::message
method
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
If you need to perform an action against a number of records, you can extend
the abstract Bulk\BaseAction
class to create your own.
Three BulkAction classes exist in the core:
To create your own BulkAction, simply create a new action class with a _bulk
method. This method takes a CakePHP Query
object as it’s first argument
<?php
namespace App\Crud\Action;
use Cake\ORM\Query;
use Crud\Action\Bulk\BaseAction;
class ApproveAction extends BaseAction
{
/**
* Set the value of the approved field to true
* for a set of entities
*
* @param \Cake\ORM\Query $query The query to act upon
* @return boolean
*/
protected function _bulk(Query $query)
{
$query->update()->set(['approved' => true]);
$statement = $query->execute();
$statement->closeCursor();
return $statement->rowCount();
}
}
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
This is a list of events emitted from actions that extend Bulk\BaseAction
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before _bulk()
is called on a Bulk Crud action.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.To abort a bulk action, simply stop the event by calling
$event->stopPropagation()
.
public function bulk($id)
{
$this->Crud->on('beforeBulk', function(\Cake\Event\Event $event) {
// Stop the bulk event, the action will not continue
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after calling _bulk()
on a Bulk Crud action.
The Crud Subject contains two keys:
true
the _bulk()
call succeeded, false
otherwiseRepository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.public function bulk($id)
{
$this->Crud->on('afterBulk', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Bulk action failed");
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
You can use the Bulk\DeleteAction
class to delete a group of database records.
<?php
namespace App\Controller;
class PostsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Crud->mapAction('deleteAll', 'Crud.Bulk/Delete');
}
}
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
This is a list of events emitted from actions that extend Bulk\BaseAction
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before _bulk()
is called on a Bulk Crud action.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.To abort a bulk action, simply stop the event by calling
$event->stopPropagation()
.
public function bulk($id)
{
$this->Crud->on('beforeBulk', function(\Cake\Event\Event $event) {
// Stop the bulk event, the action will not continue
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after calling _bulk()
on a Bulk Crud action.
The Crud Subject contains two keys:
true
the _bulk()
call succeeded, false
otherwiseRepository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.public function bulk($id)
{
$this->Crud->on('afterBulk', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Bulk action failed");
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
You can use the Bulk\SetValueAction
class to specify the value of a
given field for a group of database records.
<?php
namespace App\Controller;
class PostsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Crud->mapAction('publishAll', [
'className' => 'Crud.Bulk/SetValue',
'field' => 'status',
'value' => 'publish'
]);
}
}
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
This is a list of events emitted from actions that extend Bulk\BaseAction
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before _bulk()
is called on a Bulk Crud action.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.To abort a bulk action, simply stop the event by calling
$event->stopPropagation()
.
public function bulk($id)
{
$this->Crud->on('beforeBulk', function(\Cake\Event\Event $event) {
// Stop the bulk event, the action will not continue
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after calling _bulk()
on a Bulk Crud action.
The Crud Subject contains two keys:
true
the _bulk()
call succeeded, false
otherwiseRepository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.public function bulk($id)
{
$this->Crud->on('afterBulk', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Bulk action failed");
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
You can use the Bulk\ToggleAction
class to toggle the value of a
boolean field for a group of database records.
<?php
namespace App\Controller;
class PostsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Crud->mapAction('toggleActive', [
'className' => 'Crud.Bulk/Toggle',
'field' => 'toggle',
]);
}
}
Note
Before applying any configuration to an action
it must be mapped first.
If the action has not been mapped an exception will be raised.
Test or modify if the Crud Action is enabled or not.
When a CrudAction is disabled, Crud will not handle any requests to the action, and CakePHP will raise the normal
\Cake\Error\MissingActionException
exception if you haven’t implemented the action in your controller.
Warning
If you have enabled Crud and you are still receiving a MissingActionException
, ensure the action is enabled and
that the controller has the \Crud\Controller\ControllerTrait
implemented.
To test if an action is enabled, call the enabled
method on the action.
$this->Crud->action()->enabled();
To disable an action, call the disable
method on the action.
$this->Crud->action()->disable();
To enable an action, call the enable
method on the action.
$this->Crud->action()->enable();
To disable or enable multiple actions at the same time, Crud Component
provides helper methods.
The enable
and disable
method can take a string or an array, for easy mass-updating.
$this->Crud->enable('index');
$this->Crud->enable(['index', 'add']);
$this->Crud->disable('index');
$this->Crud->disable(['index', 'add']);
Note
These methods simply calls the enable
and disable
method in each Crud Action
class, and do not provide any magic
other than mass updating.
Warning
While it’s possible to update the enabled
property directly on an action using the config
methods,
it’s not recommend, as important cleanup logic will not be applied if you use the config()
method directly.
The 1st parameter to Table::find()
- the default value is all
.
To get the current configured findMethod
keys call the findMethod
method without any arguments.
$this->Crud->action()->findMethod();
To change the findMethod value pass a string argument to the method
$this->Crud->action()->findMethod('my_custom_finder');
This is a list of events emitted from actions that extend Bulk\BaseAction
.
Please see the events documentation for a full list of generic properties and how to use the event system correctly.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
This event is emitted before _bulk()
is called on a Bulk Crud action.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.To abort a bulk action, simply stop the event by calling
$event->stopPropagation()
.
public function bulk($id)
{
$this->Crud->on('beforeBulk', function(\Cake\Event\Event $event) {
// Stop the bulk event, the action will not continue
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after calling _bulk()
on a Bulk Crud action.
The Crud Subject contains two keys:
true
the _bulk()
call succeeded, false
otherwiseRepository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.public function bulk($id)
{
$this->Crud->on('afterBulk', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Bulk action failed");
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
If you need to customize an action for any reason, you can create your own custom Crud action class.
A Crud Action can respond to any HTTP
verb (GET
, POST
, PUT
, DELETE
).
Each HTTP verb can be implemented as method, e.g. _get()
for HTTP GET
,
_post()
for HTTP POST
and _put()
for HTTP PUT
.
If no HTTP verb specific method is found in the class, _handle()
will be executed.
A default custom index action might be as simple as the following:
<?php
namespace App\Crud\Action;
class MyIndexAction extends \Crud\Action\BaseAction
{
/**
* Default settings
*
* @var array
*/
protected $_defaultConfig = [
'enabled' => true,
'scope' => 'table',
'findMethod' => 'all',
'view' => null,
'viewVar' => null,
'serialize' => [],
'api' => [
'success' => [
'code' => 200
],
'error' => [
'code' => 400
]
]
];
/**
* Generic handler for all HTTP verbs
*
* @return void
*/
protected function _handle()
{
$query = $this->_table()->find($this->findMethod());
$items = $this->_controller()->paginate($query);
}
}
Note
In this basic example, we have removed all the events that are emitted.
The most common use-cases for a custom action class is when you need to have specific code run on all your controllers for a certain action. For example reading from the session or adjusting the query to add dynamic complex conditions.
Remember that in the Configuration you can configure your action classes on a per-action basis, so you might just want a custom action for a single action across your controllers.
Once you have created your custom action class, you can configure Crud to use it for specific actions by changing the Crud component configuration.
class AppController extends \Cake\Controller\Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('Crud.Crud', [
'actions' => [
'index' => ['className' => '\App\Crud\Action\MyIndexAction']
]
]);
}
Note
Ensure that you escape your namespace when loading your own action classes.
Events are the backbone of Crud, and your primary gateway into customization of Crud and fitting it to your applications.
You can subscribe to events from almost everywhere, and in multiple ways.
We override the implementedEvents()
method in the controller, and bind
the Crud.beforeFind
event to the _beforeFind()
method in the controller.
When using this technique, you need to prefix all the event names with Crud.
Most of the other ways to listen for events do not need this, as it’s done automatically.
namespace app\Controller;
class BlogsController extends AppController
{
public function implementedEvents()
{
return parent::implementedEvents() + ['Crud.beforeFind' => '_beforeFind'];
}
public function _beforeFind(\Cake\Event\Event $event)
{
}
}
Note
It’s important that the controller event method is public
, since it’s called
from the CakePHP event manager, outside of the Controller scope.
The added _
prefix is there only to prevent it being executed as an controller
action.
You can bind events directly in your controller actions, simply call the on()
method in Crud and provide a
callback. The example below uses a closure
for the callback, but everything that is valid for call_user_func()
can be used
public function view($id)
{
$this->Crud->on('beforeFind', function(\Cake\Event\Event $event) {
// Will only execute for the view() action
});
return $this->Crud->execute();
}
Note
When implementing events in your controller actions, it’s important to
include return $this->Crud->execute();
otherwise Crud will not process the action.
The benefit of the controller method is that you can easily share it between two actions, like below.
public function view($id)
{
$this->Crud->on('beforeFind', [$this, '_beforeFind']);
return $this->Crud->execute();
}
public function admin_view($id)
{
$this->Crud->on('beforeFind', [$this, '_beforeFind']);
return $this->Crud->execute();
}
public function _beforeFind(\Cake\Event\Event $event)
{
// Will execute for both view() and admin_view()
}
Different Crud actions will emit a different combination of events during their execution, with different Subject data. If you are looking for events specific to an action, check the specific Crud action documentation page.
This is a full list of all events emitted from Crud.
Triggered when a CrudAction
is going to handle a CakePHP request.
It’s emitted from CrudComponent::beforeFilter
and thus is fired in the same cycle as all Controller::beforeFilter
events.
Called after the Controller::beforeFilter()
and before the Crud action.
It’s emitted from CrudComponent::startup()
and thus is fired in the same cycle
as all Component::startup()
events.
This event is emitted before calling Table::delete
.
The Crud Subject contains the following keys:
Entity
from the find()
call.To abort a delete()
simply stop the event by calling
$event->stopPropagation()
.
public function delete($id)
{
$this->Crud->on('beforeDelete', function(\Cake\Event\Event $event) {
// Stop the delete event, the entity will not be deleted
if ($event->getSubject()->item->author !== 'admin') {
$event->stopPropagation();
}
});
return $this->Crud->execute();
}
This event is emitted after Table::delete()
has been called.
The Crud Subject contains two keys:
true
the delete()
call succeeded, false
otherwisepublic function delete($id)
{
$this->Crud->on('afterDelete', function(\Cake\Event\Event $event) {
if (!$event->getSubject()->success) {
$this->log("Delete failed for entity " . $event->getSubject()->id);
}
});
return $this->Crud->execute();
}
The event is emitted before calling the find method in the table.
The Crud Subject contains the following keys:
Repository
(Table
) which the query will be executed against.Query
object from the Repository
where $PrimaryKey => $IdFromRequest
is already added to the conditions.This is the last place you can modify the query before it’s executed against the database.
Note
An example
Given the URL: /posts/view/10
the repository
object will be an instance of PostsTable
and the query
includes a WHERE
condition with Posts.id = 10
After the event has emitted, the database query is executed with LIMIT 1
.
If a record is found the Crud.afterFind
event is emitted.
Warning
If no record is found in the database, the recordNotFound
event is emitted instead of Crud.afterFind
.
public function delete($id)
{
$this->Crud->on('beforeFind', function(\Cake\Event\Event $event) {
$event->getSubject()->query->where(['author' => $this->Auth->user('id')]);
});
return $this->Crud->execute();
}
After the query has been executed, and a record has been found this event is emitted.
The Crud Subject contains two keys:
id
The ID that was originally passed to the action and is usually the primary key of your model.entity
The record that was found in the database.Note
If an entity is not found, the RecordNotFound
event is emitted instead.
public function delete($id)
{
$this->Crud->on('afterFind', function(\Cake\Event\Event $event) {
$this->log("Found item: " . $event->getSubject()->entity->id . " in the database");
});
return $this->Crud->execute();
}
Note
Do not confuse this event with the beforeSave
callback in the ORM layer
Called right before calling Table::save()
.
The Crud Subject contains the following keys:
entity
object marshaled with the HTTP POST
data from the request.string
with the saveMethod
.array
with the saveOptions
.All modifications to these keys will be passed into the Table::$saveMethod
.
Warning
After this event has been emitted, changes done through the $action->saveMethod()
or $action->saveOptions()
methods will no longer affect the code, as the rest of the code uses the values from the Crud Subject
Note
Do not confuse this event with the afterSave
callback in the ORM layer.
This event is emitted right after the call to Table::save()
.
The Crud Subject contains the following keys:
Table::save()
was successful.Table::save()
call succeed or not.true
if the record was created
and false
if the record was updated
.entity
object marshaled with the HTTP POST
data from the request and the save()
logic.public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created");
} else {
$this->log("The entity was updated");
}
});
return $this->Crud->execute();
}
public function edit($id)
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->success) {
$this->log("The entity was saved successfully");
} else {
$this->log("The entity was NOT saved successfully");
}
});
return $this->Crud->execute();
}
public function add()
{
$this->Crud->on('afterSave', function(\Cake\Event\Event $event) {
if ($event->getSubject()->created) {
$this->log("The entity was created with id: " . $event->getSubject()->id);
}
});
return $this->Crud->execute();
}
This event is emitted before Controller::paginate()
is called.
public function index()
{
$this->Crud->on('beforePaginate', function(\Cake\Event\Event $event) {
$this->paginate['conditions']['is_active'] = true;
});
return $this->Crud->execute();
}
This event is emitted right after the call to Controller::paginate()
.
The entities
property of the event object contains all the database records found in the pagination call.
public function index()
{
$this->Crud->on('afterPaginate', function(\Cake\Event\Event $event) {
foreach ($event->getSubject()->entities as $entity) {
// $entity is an entity
}
});
return $this->Crud->execute();
}
Simple and event driven wrapper for Controller::redirect()
.
The Crud Subject contains the following keys:
Controller::redirect()
.Controller::redirect()
.Controller::redirect()
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to Controller::redirect()
.
The redirect $url
can be changed on the fly either by posting a redirect_url
field from your
form or by providing a redirect_url
HTTP query key.
The default for most redirects are simply to return to the index()
action.
Invoked right before the view will be rendered.
This is also before the controllers own beforeRender callback.
Note
This event will throw an exception.
The default configuration will thrown an Cake\Error\NotFoundException
which will yield a 404 response.
The event is triggered after a find
did not find any records in the database.
You can modify the exception class thrown using CrudComponent::message
method
Simple and event driven wrapper for SessionComponent::setFlash
.
The Crud Subject contains the following keys:
SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.SessionComponent::setFlash
.Entity
from the previously emitted event.All keys can be modified as you see fit, at the end of the event cycle they will be passed
directly to SessionComponent::setFlash
.
Defaults are stored in the messages
configuration array for each action.
If you do not want to use this feature, simply stop the event by calling it’s stopPropagation()
method.
If you’d like to customise the flash messages that are used, perhaps you’re using friendsofcake/bootstrap-ui. It’s actually quite simple to do, and can be done as part of the component configuration or on the fly.
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'edit' => [
'className' => 'Crud.Edit',
'messages' => [
'success' => [
'params' => ['class' => 'alert alert-success alert-dismissible']
],
'error' => [
'params' => ['class' => 'alert alert-danger alert-dismissible']
]
],
]
]
]);
}
If you’d like to configure it on the fly you can use the eventManager to change the event subject as the event is emitted.
$this->eventManager()->on('Crud.setFlash', function (Event $event) {
if ($event->getSubject()->success) {
$event->getSubject()->params['class'] = 'alert alert-success alert-dismissible';
}
});
The Crud Subject is the class which is passed as the subject of all the events that the Crud plugin emits during its execution. Depending on the action being scaffolded, and what it’s working on the contents of the subject can be different.
You can find many of the subject contents are included as part of the Core Crud Events documentation. This is because the subject of the event is very specific to the event being emitted.
When dealing with listeners, you are able to manipulate the subject of the event in order to change Crud’s behavior. Such as changing pagination, or adding extra conditions to a query.
This is an example of the data passed in a beforeFind
event subject.
<?php
public function view($id)
{
$this->Crud->on('beforeFind', function (\Cake\Event\Event $event) {
$query = $event->getSubject()->query;
$primaryKey = $event->getSubject()->id;
$table = $event->getSubject()->repository;
});
}
Find more examples in the Core Crud Events documentation, for the event you need.
Tip
While CRUD provides many listeners, it’s recommended that you add your own reusable listeners for your application needs
Listeners are the foundation for the extreme flexibility Crud provides you as an application developer.
The event system allows you to hook into the most important part of the Crud actions flow and customize it to your unique application needs.
The listener system is simply the Events System from CakePHP, and all the official documentation and usage also applies to Crud.
The Crud event system uses two methods trigger()
and on()
to interface
the underlying CakePHP event system.
The only hard requirement for a Crud listener is that it needs to either implement
the implementedEvents()
method or extend \Crud\Listener\Base
.
Below is the code for a simple Crud listener. In the next few sections we will walk through the code and explain how it works, and what every single line of code does.
For each section, the relevant lines of code will be highlighted.
All built-in listeners in Crud live in the Crud\Listener
namespace.
All listeners in Crud, including yours, should inherit from the Crud\Listener\Base
class.
This class is abstract
and provides numerous auxiliary methods which can be useful for you both as a developer and
as an action creator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace Crud\Listener;
class Example extends \Crud\Listener\BaseListener
{
/**
* Returns a list of all events that will fire in the lister during the Crud life-cycle.
*
* @return array
*/
public function implementedEvents()
{
return [
'Crud.beforeRender' => ['callable' => 'beforeRender']
];
}
/**
* Executed when Crud.beforeRender is emitted.
*
* @param \Cake\Event\Event $event Event instance
*
* @return void
*/
public function beforeRender(\Cake\Event\Event $event)
{
$this->_response()->header('X-Powered-By', 'CRUD 4.0');
}
}
|
As documented in the CakePHP Events System
all listeners must contain a implementedEvents
method.
In this example, we simply request that beforeRender
in our class is executed
every time a Crud.beforeRender
event is emitted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace Crud\Listener;
class Example extends \Crud\Listener\BaseListener
{
/**
* Returns a list of all events that will fire in the lister during the Crud life-cycle.
*
* @return array
*/
public function implementedEvents()
{
return [
'Crud.beforeRender' => ['callable' => 'beforeRender']
];
}
/**
* Executed when Crud.beforeRender is emitted.
*
* @param \Cake\Event\Event $event Event instance
*
* @return void
*/
public function beforeRender(\Cake\Event\Event $event)
{
$this->_response()->header('X-Powered-By', 'CRUD 4.0');
}
}
|
Note
The Crud.beforeRender
event is similar to the Controller and View event of the
same name, but Crud.beforeRender
is called first, and can halt the entire
rendering process
This method gets executed every time a Crud.beforeRender
event is emitted from within Crud or by you as a
developer. When the event is emitted, we append a header to the client HTTP response named X-Powered-By
with
the value CRUD 4.0
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace Crud\Listener;
class Example extends \Crud\Listener\BaseListener
{
/**
* Returns a list of all events that will fire in the lister during the Crud life-cycle.
*
* @return array
*/
public function implementedEvents()
{
return [
'Crud.beforeRender' => ['callable' => 'beforeRender']
];
}
/**
* Executed when Crud.beforeRender is emitted.
*
* @param \Cake\Event\Event $event Event instance
*
* @return void
*/
public function beforeRender(\Cake\Event\Event $event)
{
$this->_response()->header('X-Powered-By', 'CRUD 4.0');
}
}
|
Crud comes with a selection of listeners covering the most common use-cases. These allow you to tap into the events within the plugin and change behavior to suit your application, or to provide extra functionality, such as an API.
This listener allows you to easily create a JSON or XML Api built on top of Crud.
Note
The API listener
depends on the RequestHandler
to be loaded before Crud
.
Please also see the CakePHP documentation on JSON and XML views
You need to tell the Router
to parse extensions else it won’t be able toprocess and render json
and xml
URL extension.
// config/routes.php
Router::extensions(['json', 'xml']);
Ensure this statement is used before connecting any routes, and is in the routing global scope.
Attach it on the fly in your controllers beforeFilter
, this is recommended if you want to attach it only to
specific controllers and actions.
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
parent::beforeFilter($event);
$this->Crud->addListener('Crud.Api');
}
}
Attach it using components array, this is recommended if you want to attach it to all controllers, application wide.
<?php
class AppController extends Controller {
public function initialize()
{
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View'
],
'listeners' => ['Crud.Api']
]);
}
The Api Listener creates 3 new detectors in your Request
object.
Checks if the extension of the request is .json
or if the requester accepts json as part of the
HTTP accepts
header.
Checks if the extension of the request is .xml
or if the requester accepts XML as part of the HTTP accepts
header.
Checking if the request is either is('json')
or is('xml')
.
If the current request doesn’t evaluate is('api')
to true, the listener
won’t do anything at all.
All its callbacks will simply return NULL
and don’t get in your way.
The Api listener overrides the Exception.renderer
for api
requests,
so in case of an error, a standardized error will be returned, in either
json
or xml
- according to the API request type.
Create a custom exception renderer by extending the Crud’s ExceptionRenderer
class and enabling it with the exceptionRenderer
configuration option.
<?php
class AppController extends Controller {
public function initialize()
{
parent::initialize();
$this->Crud->config(['listeners.api.exceptionRenderer' => 'App\Error\ExceptionRenderer']);
}
}
Note: However if you are using CakePHP 3.3+’s PSR7 middleware feature the exceptionRenderer
config won’t be used and instead you will have to set the Error.exceptionRenderer
config in config/app.php
to 'Crud\Error\ExceptionRenderer'
as following:
'Error' => [
'errorLevel' => E_ALL,
'exceptionRenderer' => 'Crud\Error\JsonApiExceptionRenderer',
'skipLog' => [],
'log' => true,
'trace' => true,
],
The API listener will try to enforce some best practices on how an API should behave.
For a request to index
and view
the HTTP request type must be
HTTP GET
- else an MethodNotAllowed
exception will be raised.
For a request to add
the HTTP request type must be HTTP POST
-
else an MethodNotAllowed
exception will be raised.
For a request to edit
the HTTP request type must be HTTP PUT
-
else an MethodNotAllowed
exception will be raised.
For a request to delete
the HTTP request type must be HTTP DELETE
-
else an MethodNotAllowed
exception will be raised.
The default response format for both XML and JSON has two root keys, success
and data
. It’s possible to add
your own root keys simply by using _serialize
on the view var.
{
"success": true,
"data": {
}
}
<response>
<success>1</success>
<data></data>
</response>
The data.exception
key is only returned if debug
is > 0
{
"success": false,
"data": {
"code": 500,
"url": "/some/url.json",
"name": "Some exception message",
"exception": {
"class": "CakeException",
"code": 500,
"message": "Some exception message",
"trace": []
}
}
}
<response>
<success>0</success>
<data>
<code>500</code>
<url>/some/url.json</url>
<name>Some exception message</name>
<exception>
<class>CakeException</class>
<code>500</code>
<message>Some exception message</message>
<trace></trace>
<trace></trace>
</exception>
<queryLog/>
</data>
</response>
success
is based on the event->subject->success
parameter from the
Add
action.
If success
is false
a HTTP response code of 422
will be returned,
along with a list of validation errors from the model in the data
property
of the response body.
If success
is true
a HTTP response code of 201
will be returned,
along with the id of the created record in the data
property of the
response body.
The success
return data can be customized by setting the api.success.data.entity
config for the action.
//In your Controller/Action
$this->Crud->action()->config('api.success.data.entity', [
'id', //Extract the `id` value from the entity and place it into the `id` key in the return data.
'status' => 'status_value' //Extract the `status_value` value from the entity and place it into the `status` key in the return data.
]);
success
is based on the event->subject->success
parameter from the
Edit
action.
If success
is false
a HTTP response code of 422
will be returned,
along with a list of validation errors from the model in the data
property
of the response body.
If success
is true
a HTTP response code of 200
will be returned
(even when the resource has not been updated).
success
is based on the event->subject->success
parameter from
the Delete
action.
If success
is false
a HTTP response code of 400
will be returned.
If success
is true
a HTTP response code of 200
will be returned,
along with empty data
property in the response body.
In case an id
is provided to a crud action and the id does not exist in
the database, a 404
NotFoundException` will be thrown.
In case a ìd
is provided to a crud action and the id is not valid
according to the database type a 500 BadRequestException
will be thrown
Warning
This feature requires the API listener to work.
This listener appends pagination information to the API responses that is contain pagination information.
Attach this listener to your AppController components array if you want to make it available for all your controllers, application wide.
<?php
class AppController extends \Cake\Controller\Controller {
public function initialize()
{
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'listeners' => [
'Crud.Api', // Required
'Crud.ApiPagination'
]
]);
}
}
Attach it on the fly in your controller beforeFilter if you want to limit availability of the listener to specific controllers and actions.
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event)
{
$this->Crud->addListener('Crud.Api'); // Required
$this->Crud->addListener('Crud.ApiPagination');
}
}
Paginated results will include a new pagination element similar to the one below:
{
"success": true,
"data":[
],
"pagination":{
"page_count": 13,
"current_page": 1,
"count": 25,
"has_prev_page": false,
"has_next_page": true
}
}
Configure this listener by setting the CakePHP Pagination options directly to the query object.
public function index()
{
$this->Crud->on('beforePaginate', function (\Cake\Event\Event $event) {
$event->getSubject()->query->contain([
'Comments' => function ($q) {
return $q
->select(['id', 'name', 'description'])
->where([
'Comments.approved' => true
]);
}
]);
});
}
Warning
This feature requires the API listener to work.
This listener appends query log information to the API responses
Note
The listener will only append the queryLog
key if debug
is set to true.
Attach it on the fly in your controller beforeFilter, this is recommended if you want to attach it only to specific controllers and actions
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$this->Crud->addListener('Crud.Api'); // Required
$this->Crud->addListener('Crud.ApiQueryLog');
}
}
Attach it using components array, this is recommended if you want to attach it to all controllers, application wide
<?php
class AppController extends \Cake\Controller\Controller {
public function initialize()
{
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'listeners' => [
'Crud.Api', // Required
'Crud.ApiQueryLog'
]
]);
}
}
Paginated results will include a
{
"success": true,
"data": [
],
"queryLog": {
"default": {
"log": [
{
"query": "SELECT SOMETHING FROM SOMEWHERE",
"took": 2,
"params": [
],
"affected": 25,
"numRows": 25
},
{
"query": "SELECT SOMETHING FROM SOMEWHERE'",
"params": [
],
"affected": 1,
"numRows": 1,
"took": 0
}
]
}
}
}
The Crud JsonApi listener allows you to create APIs that produce JSON API compliant output.
Please note that using this listener requires adding it to your application by:
Enable more complex redirect rules.
Attach it on the fly in your controller beforeFilter, this is recommended if you want to attach it only to specific controllers and actions:
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$this->Crud->addListener('Crud.Redirect');
parent::beforeFilter($event);
}
}
Attach it using components array, this is recommended if you want to attach it to all controllers, application wide:
<?php
class SamplesController extends AppController {
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'index',
'view'
],
'listeners' => [
'Crud.Redirect'
]
]);
}
}
A reader is a closure that can access a field in an object through different means.
Below is a list of the build-in readers you can use:
Name | Pseudo code | Description |
---|---|---|
request.key |
$this->request->{$field} |
Access a property directly on the Request object |
request.data |
$this->request->data($field) |
Access a HTTP POST data field using Hash::get() compatible format |
request.query |
$this->request->query($field) |
Access a HTTP query argument using Hash::get() compatible format |
model.key |
$Model->{$field} |
Access a property directly on the Model instance |
model.data |
$Model->data[$field] |
Access a model data key using Hash::get() compatible format |
model.field |
$Model->field($field) |
Access a model key by going to the database and read the value |
subject.key |
$CrudSubject->{$key} |
Access a property directly on the event subject |
Adding or overriding a reader is very simple.
The closure takes two arguments:
CrudSubject $subject
$key = null
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$listener = $this->Crud->listener('Redirect');
$listener->reader($name, Closure $closure);
// Example on a reader using Configure
$listener->reader('configure.key', function(CrudSubject $subject, $key) {
return Configure::read($key);
});
parent::beforeFilter();
}
}
?>
Below is the defaults provided by build-in Crud actions:
By default Add Crud Action always redirect to array('action' => 'index')
on afterSave
Name | Reader | Key | Result | Description |
---|---|---|---|---|
post_add |
request.data |
_add |
array('action' => 'add') |
By providing _add as a post key, the user will be redirected back to the add action |
post_edit |
request.data |
_edit |
array('action' => 'edit', $id) |
By providing _edit as a post key, the user will be redirected to the edit action with the newly created ID as parameter |
By default Edit Crud Action always redirect to array('action' => 'index')
on afterSave
Name | Reader | Key | Result | Description |
---|---|---|---|---|
post_add |
request.data |
_add |
array('action' => 'add') |
By providing _add as a post key, the user will be redirected back to the add action |
post_edit |
request.data |
_edit |
array('action' => 'edit', $id) |
By providing _edit as a post key, the user will be redirected to the edit action with the same ID as parameter as the current URL |
It’s very simple to modify existing or add your own redirect rules:
<?php
class SamplesController extends AppController
{
public function beforeFilter(\Cake\Event\Event $event)
{
// Get all the redirect rules
$rules = $this->Crud->action()->redirectConfig();
// Get one named rule only
$rule = $this->Crud->action()->redirectConfig('add');
// Configure a redirect rule:
//
// if $_POST['_view'] is set then redirect to
// 'view' action with the value of '$subject->id'
$this->Crud->action()->redirectConfig('view',
[
'reader' => 'request.data', // Any reader from the list above
'key' => '_view', // The key to check for, passed to the reader
'url' => [ // The url to redirect to
'action' => 'view', // The final url will be '/view/$id'
['subject.key', 'id'] // If an array is encountered, it will be expanded the same was as 'reader'+'key'
]
]
);
parent::beforeFilter($event);
}
}
This listener provides search capabilities for the Crud plugin.
The Search listener depends on the friendsofcake/search package.
composer require friendsofcake/search
Attach it on the fly in your controllers beforeFilter, this is recommended if you want to attach it only to specific controllers and actions.
<?php
class SamplesController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$this->Crud->addListener('Crud.Search', [
// Events to listen for and apply search finder to query.
'enabled' => [
'Crud.beforeLookup',
'Crud.beforePaginate'
],
// Search collection to use
'collection' => 'default'
]);
parent::beforeFilter($event);
}
}
Attach it using components array, this is recommended if you want to attach it to all controllers, application wide.
<?php
class DemoController extends AppController {
public function initialize()
{
$this->loadComponent('Crud.Crud', [
'actions' => [
'index'
],
'listeners' => [
'Crud.Search'
]
]);
}
}
Any class can be used as a Crud Listener, even the controller.
We override the implementedEvents()
method in the controller, and bind
the Crud.beforeFind
event to the _beforeFind()
method in the controller.
<?php
namespace App\Controller;
class BlogsController extends AppController {
public function implementedEvents()
{
return parent::implementedEvents() + [
'Crud.beforeFind' => '_beforeFind'
];
}
public function _beforeFind(\Cake\Event\Event $event, \Cake\ORM\Query $query)
{
}
}
Creating your own listener class is very similar to using a controller as a listener.
<?php
namespace App\Lib\Listeners;
use Cake\Event\Event;
use Crud\Listener\BaseListener;
class MyListener extends BaseListener
{
public function implementedEvents()
{
return [
'Crud.beforeFind' => '_beforeFind'
];
}
public function _beforeFind(Event $event)
{
Log::debug('Inside the listener!');
}
}
Creating a REST API using Crud is very easy and just requires adding an API Listener to your application.
If you’d like to get started right away with the API Listener, please check the Listeners chapter.
To get started with the JSON API Listener, please check the listener’s documentation.
To ease with unit testing of Crud Listeners and Crud Actions, it’s recommended to use the proxy methods found in [CrudBaseObject]({{site.url}}/api/develop/class-CrudBaseObject.html).
These methods are much easier to mock than the full CrudComponent object.
They also allow you to just mock the methods you need for your specific test, rather than the big dependency nightmare the CrudComponent can be in some cases.
These methods are available in all CrudAction and CrudListener objects.
Trigger a Crud Event
$this->_trigger('beforeSave')
$this->_trigger('beforeSave', ['data' => 'keys'])
$this->_trigger('beforeSave', $this->_subject(['data' => 'keys']))
Create a Crud Subject - used in $this->_trigger
$this->_subject()
$this->_subject(['data' => 'keys'])