Controllers
-
class Cake\Controller\Controller
Controllers are the ‘C’ in MVC. After routing has been applied and the correct
controller has been found, your controller’s action is called. Your controller
should handle interpreting the request data, making sure the correct models
are called, and the right response or view is rendered. Controllers can be
thought of as middle layer between the Model and View. You want to keep your
controllers thin, and your models fat. This will help you reuse
your code and makes your code easier to test.
Commonly, a controller is used to manage the logic around a single model. For
example, if you were building a site for an online bakery, you might have a
RecipesController managing your recipes and an IngredientsController managing your
ingredients. However, it’s also possible to have controllers work with more than
one model. In CakePHP, a controller is named after the primary model it
handles.
Your application’s controllers extend the AppController
class, which in turn
extends the core Controller
class. The AppController
class can be defined in src/Controller/AppController.php and it should
contain methods that are shared between all of your application’s controllers.
Controllers provide a number of methods that handle requests. These are called
actions. By default, each public method in
a controller is an action, and is accessible from a URL. An action is responsible
for interpreting the request and creating the response. Usually responses are
in the form of a rendered view, but there are other ways to create responses as
well.
The App Controller
As stated in the introduction, the AppController
class is the parent class
to all of your application’s controllers. AppController
itself extends the
Cake\Controller\Controller
class included in CakePHP.
AppController
is defined in src/Controller/AppController.php as
follows:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
}
Controller attributes and methods created in your AppController
will be
available in all controllers that extend it. Components (which you’ll
learn about later) are best used for code that is used in many (but not
necessarily all) controllers.
You can use your AppController
to load components that will be used in every
controller in your application. CakePHP provides a initialize()
method that
is invoked at the end of a Controller’s constructor for this kind of use:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize(): void
{
// Always enable the FormProtection component.
$this->loadComponent('FormProtection');
}
}
Request Flow
When a request is made to a CakePHP application, CakePHP’s
Cake\Routing\Router
and Cake\Routing\Dispatcher
classes use Connecting Routes to find and create the correct
controller instance. The request data is encapsulated in a request object.
CakePHP puts all of the important request information into the $this->request
property. See the section on Request for more information on the
CakePHP request object.
Controller Actions
Controller actions are responsible for converting the request parameters into a
response for the browser/user making the request. CakePHP uses conventions to
automate this process and remove some boilerplate code you would otherwise need
to write.
By convention, CakePHP renders a view with an inflected version of the action
name. Returning to our online bakery example, our RecipesController might contain the
view()
, share()
, and search()
actions. The controller would be found
in src/Controller/RecipesController.php and contain:
// src/Controller/RecipesController.php
class RecipesController extends AppController
{
public function view($id)
{
// Action logic goes here.
}
public function share($customerId, $recipeId)
{
// Action logic goes here.
}
public function search($query)
{
// Action logic goes here.
}
}
The template files for these actions would be templates/Recipes/view.php,
templates/Recipes/share.php, and templates/Recipes/search.php. The
conventional view file name is the lowercased and underscored version of the
action name.
Controller actions generally use
Controller::set()
to create a context that
View
uses to render the view layer. Because of the conventions that
CakePHP uses, you don’t need to create and render the view manually. Instead,
once a controller action has completed, CakePHP will handle rendering and
delivering the View.
If for some reason you’d like to skip the default behavior, you can return a
Cake\Http\Response
object from the action with the fully
created response.
In order for you to use a controller effectively in your own application, we’ll
cover some of the core attributes and methods provided by CakePHP’s controllers.
Interacting with Views
Controllers interact with views in a number of ways. First, they
are able to pass data to the views, using Controller::set()
. You can also
decide which view class to use, and which view file should be
rendered from the controller.
Setting View Variables
-
Cake\Controller\Controller::set(string $var, mixed $value)
The Controller::set()
method is the main way to send data from your
controller to your view. Once you’ve used Controller::set()
, the variable
can be accessed in your view:
// First you pass data from the controller:
$this->set('color', 'pink');
// Then, in the view, you can utilize the data:
?>
You have selected <?= h($color) ?> icing for the cake.
The Controller::set()
method also takes an
associative array as its first parameter. This can often be a quick way to
assign a set of information to the view:
$data = [
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95,
];
// Make $color, $type, and $base_price
// available to the view:
$this->set($data);
Keep in mind that view vars are shared among all parts rendered by your view.
They will be available in all parts of the view: the template, the layout and
all elements inside the former two.
Setting View Options
If you want to customize the view class, layout/template paths, helpers or the
theme that will be used when rendering the view, you can use the
viewBuilder()
method to get a builder. This builder can be used to define
properties of the view before it is created:
$this->viewBuilder()
->addHelper('MyCustom')
->setTheme('Modern')
->setClassName('Modern.Admin');
The above shows how you can load custom helpers, set the theme and use a custom
view class.
Rendering a View
-
Cake\Controller\Controller::render(string $view, string $layout)
The Controller::render()
method is automatically called at the end of each requested
controller action. This method performs all the view logic (using the data
you’ve submitted using the Controller::set()
method), places the view inside its
View::$layout
, and serves it back to the end user.
The default view file used by render is determined by convention.
If the search()
action of the RecipesController is requested,
the view file in templates/Recipes/search.php will be rendered:
namespace App\Controller;
class RecipesController extends AppController
{
// ...
public function search()
{
// Render the view in templates/Recipes/search.php
return $this->render();
}
// ...
}
Although CakePHP will automatically call it after every action’s logic
(unless you’ve called $this->disableAutoRender()
), you can use it to specify
an alternate view file by specifying a view file name as first argument of
Controller::render()
method.
If $view
starts with ‘/’, it is assumed to be a view or
element file relative to the templates folder. This allows
direct rendering of elements, very useful in AJAX calls:
// Render the element in templates/element/ajaxreturn.php
$this->render('/element/ajaxreturn');
The second parameter $layout
of Controller::render()
allows you to specify the layout
with which the view is rendered.
Rendering a Specific Template
In your controller, you may want to render a different view than the
conventional one. You can do this by calling Controller::render()
directly. Once you
have called Controller::render()
, CakePHP will not try to re-render the view:
namespace App\Controller;
class PostsController extends AppController
{
public function my_action()
{
$this->render('custom_file');
}
}
This would render templates/Posts/custom_file.php instead of
templates/Posts/my_action.php.
You can also render views inside plugins using the following syntax:
$this->render('PluginName.PluginController/custom_file')
.
For example:
namespace App\Controller;
class PostsController extends AppController
{
public function myAction()
{
$this->render('Users.UserDetails/custom_file');
}
}
This would render plugins/Users/templates/UserDetails/custom_file.php
Content Type Negotiation
-
Cake\Controller\Controller::addViewClasses()
Controllers can define a list of view classes they support. After the
controller’s action is complete CakePHP will use the view list to perform
content-type negotiation with either Routing File Extensions or Accept
headers. This enables your application to re-use the same controller action to
render an HTML view or render a JSON or XML response. To define the list of
supported view classes for a controller is done with the addViewClasses()
method:
namespace App\Controller;
use Cake\View\JsonView;
use Cake\View\XmlView;
class PostsController extends AppController
{
public function initialize(): void
{
parent::initialize();
$this->addViewClasses([JsonView::class, XmlView::class]);
}
}
The application’s View
class is automatically used as a fallback when no
other view can be selected based on the request’s Accept
header or routing
extension. If your application only supports content types for a specific
actions, you can call addClasses()
within your action too:
public function export(): void
{
// Use a custom CSV view for data exports.
$this->addViewClasses([CsvView::class]);
// Rest of the action code
}
If within your controller actions you need to process the request or load data
differently based on the content type you can use
Checking Request Conditions:
// In a controller action
// Load additional data when preparing JSON responses
if ($this->request->is('json')) {
$query->contain('Authors');
}
In case your app need more complex logic to decide which view classes to use
then you can override the Controller::viewClasses()
method and return
an array of view classes as required.
Note
View classes must implement the static contentType()
hook method to
participate in content-type negotiation.
Content Type Negotiation Fallbacks
If no View can be matched with the request’s content type preferences, CakePHP
will use the base View
class. If you want to require content-type
negotiation, you can use the NegotiationRequiredView
which sets a 406
status
code:
public function initialize(): void
{
parent::initialize();
// Require Accept header negotiation or return a 406 response.
$this->addViewClasses([JsonView::class, NegotiationRequiredView::class]);
}
You can use the TYPE_MATCH_ALL
content type value to build your own fallback
view logic:
namespace App\View;
use Cake\View\View;
class CustomFallbackView extends View
{
public static function contentType(): string
{
return static::TYPE_MATCH_ALL;
}
}
It is important to remember that match-all views are applied only after
content-type negotiation is attempted.
Using AjaxView
In applications that use hypermedia or AJAX clients, you often need to render
view contents without the wrapping layout. You can use the AjaxView
that
is bundled with the application skeleton:
// In a controller action, or in beforeRender.
if ($this->request->is('ajax')) {
$this->viewBuilder()->setClassName('Ajax');
}
AjaxView
will respond as text/html
and use the ajax
layout.
Generally this layout is minimal or contains client specific markup. This
replaces usage of RequestHandlerComponent
automatically using the
AjaxView
in 4.x.
Redirecting to Other Pages
-
Cake\Controller\Controller::redirect(string|array $url, integer $status)
The redirect()
method adds a Location
header and sets the status code of
a response and returns it. You should return the response created by
redirect()
to have CakePHP send the redirect instead of completing the
controller action and rendering a view.
You can redirect using routing array values:
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
$order->id,
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);
Or using a relative or absolute URL:
return $this->redirect('/orders/confirm');
return $this->redirect('http://www.example.com');
Or to the referer page:
return $this->redirect($this->referer());
By using the second parameter you can define a status code for your redirect:
// Do a 301 (moved permanently)
return $this->redirect('/order/confirm', 301);
// Do a 303 (see other)
return $this->redirect('/order/confirm', 303);
See the Using Redirects in Component Events section for how to redirect out of
a life-cycle handler.
Loading Additional Tables/Models
-
Cake\Controller\Controller::fetchTable(string $alias, array $config = [])
The fetchTable()
method comes handy when you need to use an ORM table that is not
the controller’s default one:
// In a controller method.
$recentArticles = $this->fetchTable('Articles')->find('all',
limit: 5,
order: 'Articles.created DESC'
)
->all();
-
Cake\Controller\Controller::fetchModel(string|null $modelClass = null, string|null $modelType = null)
The fetchModel()
method is useful to load non ORM models or ORM tables that
are not the controller’s default:
// ModelAwareTrait need to be explicity added to your controler first for fetchModel() to work.
use ModelAwareTrait;
// Get an ElasticSearch model
$articles = $this->fetchModel('Articles', 'Elastic');
// Get a webservices model
$github = $this->fetchModel('GitHub', 'Webservice');
// If you skip the 2nd argument it will by default try to load a ORM table.
$authors = $this->fetchModel('Authors');
Paginating a Model
-
Cake\Controller\Controller::paginate()
This method is used for paginating results fetched by your models.
You can specify page sizes, model find conditions and more. See the
pagination section for more details on
how to use paginate()
.
The $paginate
attribute gives you a way to customize how paginate()
behaves:
class ArticlesController extends AppController
{
protected array $paginate = [
'Articles' => [
'conditions' => ['published' => 1],
],
];
}
Configuring Components to Load
-
Cake\Controller\Controller::loadComponent($name, $config = [])
In your Controller’s initialize()
method you can define any components you
want loaded, and any configuration data for them:
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Flash');
$this->loadComponent('Comments', Configure::read('Comments'));
}
Request Life-cycle Callbacks
CakePHP controllers trigger several events/callbacks that you can use to insert
logic around the request life-cycle:
Controller Callback Methods
By default the following callback methods are connected to related events if the
methods are implemented by your controllers
-
Cake\Controller\Controller::beforeFilter(EventInterface $event)
Called during the Controller.initialize
event which occurs before every
action in the controller. It’s a handy place to check for an active session
or inspect user permissions.
Note
The beforeFilter() method will be called for missing actions.
Returning a response from a beforeFilter
method will not prevent other
listeners of the same event from being called. You must explicitly
stop the event.
-
Cake\Controller\Controller::beforeRender(EventInterface $event)
Called during the Controller.beforeRender
event which occurs after
controller action logic, but before the view is rendered. This callback is
not used often, but may be needed if you are calling
render()
manually before the end
of a given action.
-
Cake\Controller\Controller::afterFilter(EventInterface $event)
Called during the Controller.shutdown
event which is triggered after
every controller action, and after rendering is complete. This is the last
controller method to run.
In addition to controller life-cycle callbacks, Components
also provide a similar set of callbacks.
Remember to call AppController
’s callbacks within child controller callbacks
for best results:
//use Cake\Event\EventInterface;
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
}
Controller Middleware
-
Cake\Controller\Controller::middleware($middleware, array $options = [])
Middleware can be defined globally, in
a routing scope or within a controller. To define middleware for a specific
controller use the middleware()
method from your controller’s
initialize()
method:
public function initialize(): void
{
parent::initialize();
$this->middleware(function ($request, $handler) {
// Do middleware logic.
// Make sure you return a response or call handle()
return $handler->handle($request);
});
}
Middleware defined by a controller will be called before beforeFilter()
and action methods are called.