| title | Controllers |
|---|---|
| description | Learn CakePHP controllers: handle requests, render views, manage components, use middleware, and implement request lifecycle callbacks in MVC architecture. |
class Cake\Controller\Controller
Controllers are the 'C' in MVC. After routing is applied and the correct controller is found, your controller's action is called. Your controller should interpret the request, ensure the right models are called, and return the appropriate response or view. Controllers sit between Model and View.
::: tip Keep Controllers Thin Move heavy business logic into models and services. Thin controllers are easier to test and reuse. :::
Commonly, a controller manages logic around a single model. For example, for an online bakery you might have a RecipesController managing recipes and an IngredientsController managing 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.
::: info At a Glance
- Controllers coordinate request handling, model calls, and responses.
- Each public method is an action by default.
- Shared logic goes into
AppController.
:::
Your application's controllers extend the AppController class, which extends
the core Controller class. The AppController class can be defined in
src/Controller/AppController.php and should contain methods shared between
all of your application's controllers.
Controllers provide 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 interprets the request and creates the response. Usually responses are rendered views, but there are other response types as well.
::: details Naming and Conventions
- Controller names are plural, e.g.
RecipesController. - Action names map to view templates by convention.
- Use
AppControllerfor shared behavior.
:::
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');
}
}When a request is made to a CakePHP application, CakePHP's
Cake\Routing\Router and Cake\Routing\Dispatcher
classes use Routes Configuration to find and create the correct
controller instance. The request data is encapsulated in a request object.
CakePHP puts all the important request information into the $this->request
property. See the section on Cake Request for more information on the
CakePHP request object.
::: info Request Flow Summary
- Routes map URLs to a controller/action.
- The request data is available via
$this->request. - The action returns a response (often a rendered view).
:::
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.
::: tip Explicit Responses
Return a Response when you need full control (JSON, file downloads, or
custom status codes).
:::
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.
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.
method Cake\Controller\Controller::set(string $var, mixed $value): void
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:
::: code-group
// 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.
::: tip View Data Scope View variables are shared with the layout and elements. Prefer specific keys to avoid accidental collisions. :::
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.
By default, view options set via ViewBuilder are deep-merged with the View
class's default configuration. You can control this behavior using
setConfigMergeStrategy():
use Cake\View\ViewBuilder;
$this->viewBuilder()
->setConfigMergeStrategy(ViewBuilder::MERGE_SHALLOW)
->setOption('customArray', ['a', 'b', 'c']);Available strategies are:
ViewBuilder::MERGE_DEEP- Recursive merge (default). Nested arrays are merged together.ViewBuilder::MERGE_SHALLOW- Simple array merge. Array values are replaced rather than deep-merged.
You can retrieve the current strategy using getConfigMergeStrategy().
::: details When to Change Merge Strategy
- Use shallow merges for small, explicit overrides.
- Use deep merges when you want to extend nested defaults.
:::
::: info Added in version 5.3.0
ViewBuilder::setConfigMergeStrategy() and ViewBuilder::getConfigMergeStrategy() were added.
:::
method Cake\Controller\Controller::render(string $view, string $layout): Response
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.
::: tip Skipping Auto-Render
Call $this->disableAutoRender() when the action fully handles the response.
:::
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:
::: code-group
// Render the element in templates/element/ajaxreturn.php
return $this->render('/element/ajaxreturn');namespace App\Controller;
class PostsController extends AppController
{
public function myAction()
{
return $this->render('custom_file');
}
}namespace App\Controller;
class PostsController extends AppController
{
public function myAction()
{
return $this->render('Users.UserDetails/custom_file');
}
}:::
The second parameter $layout of Controller::render() allows you to specify the layout
with which the view is rendered.
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.
This renders templates/Posts/custom_file.php instead of
templates/Posts/my_action.php. Rendering plugin templates uses the syntax
$this->render('Users.UserDetails/custom_file') and renders
plugins/Users/templates/UserDetails/custom_file.php.
method Cake\Controller\Controller::addViewClasses(array $viewClasses): static
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 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:
::: info Content Negotiation
Use addViewClasses() to serve multiple formats from the same action.
:::
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 Check The Request:
// 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.
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.
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.
method Cake\Controller\Controller::redirect(string|array $url, integer $status): Response|null
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:
::: code-group
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
$order->id,
'?' => [
'product' => 'pizza',
'quantity' => 5,
],
'#' => 'top',
]);return $this->redirect('/orders/confirm');return $this->redirect('https://www.example.com');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 Redirect Component Events section for how to redirect out of a life-cycle handler.
method Cake\Controller\Controller::fetchTable(?string $alias = null, array $options = []): Table
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();method Cake\Controller\Controller::fetchModel(?string $modelClass = null, ?string $modelType = null): object
The fetchModel() method is useful to load non ORM models or ORM tables that
are not the controller's default:
// ModelAwareTrait need to be explicitly added to your controller 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');::: info Added in version 4.5.0 :::
method Cake\Controller\Controller::paginate(): PaginatedInterface
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],
],
];
}method Cake\Controller\Controller::loadComponent(string $name, array $config = []): Component
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'));
}CakePHP controllers trigger several events/callbacks that you can use to insert logic around the request life-cycle.
::: details Event List
Controller.initializeController.startupController.beforeRedirectController.beforeRenderController.shutdown
:::
By default, the following callback methods are connected to related events if the methods are implemented by your controllers.
method Cake\Controller\Controller::beforeFilter(EventInterface $event): void
method Cake\Controller\Controller::beforeRender(EventInterface $event): void
method Cake\Controller\Controller::afterFilter(EventInterface $event): void
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): void
{
parent::beforeFilter($event);
}To redirect from within a controller callback method you can use the following:
public function beforeFilter(EventInterface $event): void
{
if ($this->request->getParam('prefix') !== 'Admin') {
$event->setResult($this->redirect('/'));
return;
}
// Normal request handling continues.
}By setting a redirect as event result you let CakePHP know that you don't want any other component callbacks to run, and that the controller should not handle the action any further.
As of 4.1.0 you can also raise a RedirectException to signal a redirect.
method Cake\Controller\Controller::middleware(MiddlewareInterface|Closure|string $middleware, array $options = []): void
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.