request`. The request object * contains all the POST, GET and FILES that were part of the request. * * After performing the required action, controllers are responsible for * creating a response. This usually takes the form of a generated `View`, or * possibly a redirection to another URL. In either case `$this->response` * allows you to manipulate all aspects of the response. * * Controllers are created by `Dispatcher` based on request parameters and * routing. By default controllers and actions use conventional names. * For example `/posts/index` maps to `PostsController::index()`. You can re-map * URLs using Router::connect() or RouterBuilder::connect(). * * ### Life cycle callbacks * * CakePHP fires a number of life cycle callbacks during each request. * By implementing a method you can receive the related events. The available * callbacks are: * * - `beforeFilter(Event $event)` * Called before each action. This is a good place to do general logic that * applies to all actions. * - `beforeRender(Event $event)` * Called before the view is rendered. * - `beforeRedirect(Event $event, $url, Response $response)` * Called before a redirect is done. * - `afterFilter(Event $event)` * Called after each action is complete and after the view is rendered. * * @property \Cake\Controller\Component\AuthComponent $Auth * @property \Cake\Controller\Component\CookieComponent $Cookie * @property \Cake\Controller\Component\CsrfComponent $Csrf * @property \Cake\Controller\Component\FlashComponent $Flash * @property \Cake\Controller\Component\PaginatorComponent $Paginator * @property \Cake\Controller\Component\RequestHandlerComponent $RequestHandler * @property \Cake\Controller\Component\SecurityComponent $Security * @method bool isAuthorized($user) * @link https://book.cakephp.org/3.0/en/controllers.html */ class Controller implements EventListenerInterface, EventDispatcherInterface { use EventDispatcherTrait; use LocatorAwareTrait; use LogTrait; use MergeVariablesTrait; use ModelAwareTrait; use RequestActionTrait; use ViewVarsTrait; /** * The name of this controller. Controller names are plural, named after the model they manipulate. * * Set automatically using conventions in Controller::__construct(). * * @var string */ protected $name; /** * An array containing the names of helpers this controller uses. The array elements should * not contain the "Helper" part of the class name. * * Example: * ``` * public $helpers = ['Form', 'Html', 'Time']; * ``` * * @var array * @link https://book.cakephp.org/3.0/en/controllers.html#configuring-helpers-to-load * * @deprecated 3.0.0 You should configure helpers in your AppView::initialize() method. */ public $helpers = []; /** * An instance of a \Cake\Http\ServerRequest object that contains information about the current request. * This object contains all the information about a request and several methods for reading * additional information about the request. * * @var \Cake\Http\ServerRequest * @link https://book.cakephp.org/3.0/en/controllers/request-response.html#request * @deprecated 3.6.0 The property will become protected in 4.0.0. Use getRequest()/setRequest instead. */ public $request; /** * An instance of a Response object that contains information about the impending response * * @var \Cake\Http\Response * @link https://book.cakephp.org/3.0/en/controllers/request-response.html#response * @deprecated 3.6.0 The property will become protected in 4.0.0. Use getResponse()/setResponse instead. */ public $response; /** * The class name to use for creating the response object. * * @var string */ protected $_responseClass = 'Cake\Http\Response'; /** * Settings for pagination. * * Used to pre-configure pagination preferences for the various * tables your controller will be paginating. * * @var array * @see \Cake\Controller\Component\PaginatorComponent */ public $paginate = []; /** * Set to true to automatically render the view * after action logic. * * @var bool */ protected $autoRender = true; /** * Instance of ComponentRegistry used to create Components * * @var \Cake\Controller\ComponentRegistry */ protected $_components; /** * Array containing the names of components this controller uses. Component names * should not contain the "Component" portion of the class name. * * Example: * ``` * public $components = ['RequestHandler', 'Acl']; * ``` * * @var array * @link https://book.cakephp.org/3.0/en/controllers/components.html * * @deprecated 3.0.0 You should configure components in your Controller::initialize() method. */ public $components = []; /** * Instance of the View created during rendering. Won't be set until after * Controller::render() is called. * * @var \Cake\View\View * @deprecated 3.1.0 Use viewBuilder() instead. */ public $View; /** * These Controller properties will be passed from the Controller to the View as options. * * @var array * @see \Cake\View\View */ protected $_validViewOptions = [ 'passedArgs' ]; /** * Automatically set to the name of a plugin. * * @var string|null */ protected $plugin; /** * Holds all passed params. * * @var array * @deprecated 3.1.0 Use `$this->request->getParam('pass')` instead. */ public $passedArgs = []; /** * Constructor. * * Sets a number of properties based on conventions if they are empty. To override the * conventions CakePHP uses you can define properties in your class declaration. * * @param \Cake\Http\ServerRequest|null $request Request object for this controller. Can be null for testing, * but expect that features that use the request parameters will not work. * @param \Cake\Http\Response|null $response Response object for this controller. * @param string|null $name Override the name useful in testing when using mocks. * @param \Cake\Event\EventManager|null $eventManager The event manager. Defaults to a new instance. * @param \Cake\Controller\ComponentRegistry|null $components The component registry. Defaults to a new instance. */ public function __construct(ServerRequest $request = null, Response $response = null, $name = null, $eventManager = null, $components = null) { if ($name !== null) { $this->name = $name; } if ($this->name === null && $request && $request->getParam('controller')) { $this->name = $request->getParam('controller'); } if ($this->name === null) { list(, $name) = namespaceSplit(get_class($this)); $this->name = substr($name, 0, -10); } $this->setRequest($request ?: new ServerRequest()); $this->setResponse($response ?: new Response()); if ($eventManager !== null) { $this->setEventManager($eventManager); } $this->modelFactory('Table', [$this->getTableLocator(), 'get']); $plugin = $this->request->getParam('plugin'); $modelClass = ($plugin ? $plugin . '.' : '') . $this->name; $this->_setModelClass($modelClass); if ($components !== null) { $this->components($components); } $this->initialize(); $this->_mergeControllerVars(); $this->_loadComponents(); $this->getEventManager()->on($this); } /** * Initialization hook method. * * Implement this method to avoid having to overwrite * the constructor and call parent. * * @return void */ public function initialize() { } /** * Get the component registry for this controller. * * If called with the first parameter, it will be set as the controller $this->_components property * * @param \Cake\Controller\ComponentRegistry|null $components Component registry. * * @return \Cake\Controller\ComponentRegistry */ public function components($components = null) { if ($components === null && $this->_components === null) { $this->_components = new ComponentRegistry($this); } if ($components !== null) { $components->setController($this); $this->_components = $components; } return $this->_components; } /** * Add a component to the controller's registry. * * This method will also set the component to a property. * For example: * * ``` * $this->loadComponent('Acl.Acl'); * ``` * * Will result in a `Toolbar` property being set. * * @param string $name The name of the component to load. * @param array $config The config for the component. * @return \Cake\Controller\Component */ public function loadComponent($name, array $config = []) { list(, $prop) = pluginSplit($name); return $this->{$prop} = $this->components()->load($name, $config); } /** * Magic accessor for model autoloading. * * @param string $name Property name * @return bool|object The model instance or false */ public function __get($name) { $deprecated = [ 'name' => 'getName', 'plugin' => 'getPlugin', 'autoRender' => 'isAutoRenderEnabled', ]; if (isset($deprecated[$name])) { $method = $deprecated[$name]; deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->%s instead.', $name, $method)); return $this->{$method}(); } $deprecated = [ 'layout' => 'getLayout', 'view' => 'getTemplate', 'theme' => 'getTheme', 'autoLayout' => 'isAutoLayoutEnabled', 'viewPath' => 'getTemplatePath', 'layoutPath' => 'getLayoutPath', ]; if (isset($deprecated[$name])) { $method = $deprecated[$name]; deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->viewBuilder()->%s() instead.', $name, $method)); return $this->viewBuilder()->{$method}(); } list($plugin, $class) = pluginSplit($this->modelClass, true); if ($class !== $name) { return false; } return $this->loadModel($plugin . $class); } /** * Magic setter for removed properties. * * @param string $name Property name. * @param mixed $value Value to set. * @return void */ public function __set($name, $value) { $deprecated = [ 'name' => 'setName', 'plugin' => 'setPlugin' ]; if (isset($deprecated[$name])) { $method = $deprecated[$name]; deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->%s() instead.', $name, $method)); $this->{$method}($value); return; } if ($name === 'autoRender') { $value ? $this->enableAutoRender() : $this->disableAutoRender(); deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->enableAutoRender/disableAutoRender() instead.', $name)); return; } $deprecated = [ 'layout' => 'setLayout', 'view' => 'setTemplate', 'theme' => 'setTheme', 'autoLayout' => 'enableAutoLayout', 'viewPath' => 'setTemplatePath', 'layoutPath' => 'setLayoutPath', ]; if (isset($deprecated[$name])) { $method = $deprecated[$name]; deprecationWarning(sprintf('Controller::$%s is deprecated. Use $this->viewBuilder()->%s() instead.', $name, $method)); $this->viewBuilder()->{$method}($value); return; } $this->{$name} = $value; } /** * Returns the controller name. * * @return string * @since 3.6.0 */ public function getName() { return $this->name; } /** * Sets the controller name. * * @param string $name Controller name. * @return $this * @since 3.6.0 */ public function setName($name) { $this->name = $name; return $this; } /** * Returns the plugin name. * * @return string|null * @since 3.6.0 */ public function getPlugin() { return $this->plugin; } /** * Sets the plugin name. * * @param string $name Plugin name. * @return $this * @since 3.6.0 */ public function setPlugin($name) { $this->plugin = $name; return $this; } /** * Returns true if an action should be rendered automatically. * * @return bool * @since 3.6.0 */ public function isAutoRenderEnabled() { return $this->autoRender; } /** * Enable automatic action rendering. * * @return $this * @since 3.6.0 */ public function enableAutoRender() { $this->autoRender = true; return $this; } /** * Disbale automatic action rendering. * * @return $this * @since 3.6.0 */ public function disableAutoRender() { $this->autoRender = false; return $this; } /** * Gets the request instance. * * @return \Cake\Http\ServerRequest * @since 3.6.0 */ public function getRequest() { return $this->request; } /** * Sets the request objects and configures a number of controller properties * based on the contents of the request. Controller acts as a proxy for certain View variables * which must also be updated here. The properties that get set are: * * - $this->request - To the $request parameter * - $this->passedArgs - Same as $request->params['pass] * * @param \Cake\Http\ServerRequest $request Request instance. * @return $this */ public function setRequest(ServerRequest $request) { $this->request = $request; $this->plugin = $request->getParam('plugin') ?: null; if ($request->getParam('pass')) { $this->passedArgs = $request->getParam('pass'); } return $this; } /** * Gets the response instance. * * @return \Cake\Http\Response * @since 3.6.0 */ public function getResponse() { return $this->response; } /** * Sets the response instance. * * @param \Cake\Http\Response $response Response instance. * @return $this * @since 3.6.0 */ public function setResponse(Response $response) { $this->response = $response; return $this; } /** * Dispatches the controller action. Checks that the action * exists and isn't private. * * @return mixed The resulting response. * @throws \LogicException When request is not set. * @throws \Cake\Controller\Exception\MissingActionException When actions are not defined or inaccessible. */ public function invokeAction() { $request = $this->request; if (!isset($request)) { throw new LogicException('No Request object configured. Cannot invoke action'); } if (!$this->isAction($request->getParam('action'))) { throw new MissingActionException([ 'controller' => $this->name . 'Controller', 'action' => $request->getParam('action'), 'prefix' => $request->getParam('prefix') ?: '', 'plugin' => $request->getParam('plugin'), ]); } /* @var callable $callable */ $callable = [$this, $request->getParam('action')]; return $callable(...array_values($request->getParam('pass'))); } /** * Merge components, helpers vars from * parent classes. * * @return void */ protected function _mergeControllerVars() { $this->_mergeVars( ['components', 'helpers'], ['associative' => ['components', 'helpers']] ); } /** * Returns a list of all events that will fire in the controller during its lifecycle. * You can override this function to add your own listener callbacks * * @return array */ public function implementedEvents() { return [ 'Controller.initialize' => 'beforeFilter', 'Controller.beforeRender' => 'beforeRender', 'Controller.beforeRedirect' => 'beforeRedirect', 'Controller.shutdown' => 'afterFilter', ]; } /** * Loads the defined components using the Component factory. * * @return void */ protected function _loadComponents() { if (empty($this->components)) { return; } $registry = $this->components(); $components = $registry->normalizeArray($this->components); foreach ($components as $properties) { $this->loadComponent($properties['class'], $properties['config']); } } /** * Perform the startup process for this controller. * Fire the Components and Controller callbacks in the correct order. * * - Initializes components, which fires their `initialize` callback * - Calls the controller `beforeFilter`. * - triggers Component `startup` methods. * * @return \Cake\Http\Response|null */ public function startupProcess() { $event = $this->dispatchEvent('Controller.initialize'); if ($event->getResult() instanceof Response) { return $event->getResult(); } $event = $this->dispatchEvent('Controller.startup'); if ($event->getResult() instanceof Response) { return $event->getResult(); } return null; } /** * Perform the various shutdown processes for this controller. * Fire the Components and Controller callbacks in the correct order. * * - triggers the component `shutdown` callback. * - calls the Controller's `afterFilter` method. * * @return \Cake\Http\Response|null */ public function shutdownProcess() { $event = $this->dispatchEvent('Controller.shutdown'); if ($event->getResult() instanceof Response) { return $event->getResult(); } return null; } /** * Redirects to given $url, after turning off $this->autoRender. * * @param string|array $url A string or array-based URL pointing to another location within the app, * or an absolute URL * @param int $status HTTP status code (eg: 301) * @return \Cake\Http\Response|null * @link https://book.cakephp.org/3.0/en/controllers.html#Controller::redirect */ public function redirect($url, $status = 302) { $this->autoRender = false; if ($status) { $this->response = $this->response->withStatus($status); } $event = $this->dispatchEvent('Controller.beforeRedirect', [$url, $this->response]); if ($event->getResult() instanceof Response) { return $this->response = $event->getResult(); } if ($event->isStopped()) { return null; } $response = $this->response; if (!$response->getHeaderLine('Location')) { $response = $response->withLocation(Router::url($url, true)); } return $this->response = $response; } /** * Internally redirects one action to another. Does not perform another HTTP request unlike Controller::redirect() * * Examples: * * ``` * setAction('another_action'); * setAction('action_with_parameters', $parameter1); * ``` * * @param string $action The new action to be 'redirected' to. * Any other parameters passed to this method will be passed as parameters to the new action. * @param array ...$args Arguments passed to the action * @return mixed Returns the return value of the called action */ public function setAction($action, ...$args) { $this->request = $this->request->withParam('action', $action); return $this->$action(...$args); } /** * Instantiates the correct view class, hands it its data, and uses it to render the view output. * * @param string|null $view View to use for rendering * @param string|null $layout Layout to use * @return \Cake\Http\Response A response object containing the rendered view. * @link https://book.cakephp.org/3.0/en/controllers.html#rendering-a-view */ public function render($view = null, $layout = null) { $builder = $this->viewBuilder(); if (!$builder->getTemplatePath()) { $builder->setTemplatePath($this->_viewPath()); } if ($this->request->getParam('bare')) { $builder->enableAutoLayout(false); } $builder->getClassName($this->viewClass); $this->autoRender = false; $event = $this->dispatchEvent('Controller.beforeRender'); if ($event->getResult() instanceof Response) { return $event->getResult(); } if ($event->isStopped()) { return $this->response; } if ($builder->getTemplate() === null && $this->request->getParam('action')) { $builder->setTemplate($this->request->getParam('action')); } $this->View = $this->createView(); $contents = $this->View->render($view, $layout); $this->response = $this->View->response->withStringBody($contents); return $this->response; } /** * Get the viewPath based on controller name and request prefix. * * @return string */ protected function _viewPath() { $viewPath = $this->name; if ($this->request->getParam('prefix')) { $prefixes = array_map( 'Cake\Utility\Inflector::camelize', explode('/', $this->request->getParam('prefix')) ); $viewPath = implode(DIRECTORY_SEPARATOR, $prefixes) . DIRECTORY_SEPARATOR . $viewPath; } return $viewPath; } /** * Returns the referring URL for this request. * * @param string|array|null $default Default URL to use if HTTP_REFERER cannot be read from headers * @param bool $local If true, restrict referring URLs to local server * @return string Referring URL */ public function referer($default = null, $local = false) { if (!$this->request) { return Router::url($default, !$local); } $referer = $this->request->referer($local); if ($referer === '/' && $default && $default !== $referer) { $url = Router::url($default, !$local); $base = $this->request->getAttribute('base'); if ($local && $base && strpos($url, $base) === 0) { $url = substr($url, strlen($base)); if ($url[0] !== '/') { $url = '/' . $url; } return $url; } return $url; } return $referer; } /** * Handles pagination of records in Table objects. * * Will load the referenced Table object, and have the PaginatorComponent * paginate the query using the request date and settings defined in `$this->paginate`. * * This method will also make the PaginatorHelper available in the view. * * @param \Cake\ORM\Table|string|\Cake\ORM\Query|null $object Table to paginate * (e.g: Table instance, 'TableName' or a Query object) * @param array $settings The settings/configuration used for pagination. * @return \Cake\ORM\ResultSet|\Cake\Datasource\ResultSetInterface Query results * @link https://book.cakephp.org/3.0/en/controllers.html#paginating-a-model * @throws \RuntimeException When no compatible table object can be found. */ public function paginate($object = null, array $settings = []) { if (is_object($object)) { $table = $object; } if (is_string($object) || $object === null) { $try = [$object, $this->modelClass]; foreach ($try as $tableName) { if (empty($tableName)) { continue; } $table = $this->loadModel($tableName); break; } } $this->loadComponent('Paginator'); if (empty($table)) { throw new RuntimeException('Unable to locate an object compatible with paginate.'); } $settings += $this->paginate; return $this->Paginator->paginate($table, $settings); } /** * Method to check that an action is accessible from a URL. * * Override this method to change which controller methods can be reached. * The default implementation disallows access to all methods defined on Cake\Controller\Controller, * and allows all public methods on all subclasses of this class. * * @param string $action The action to check. * @return bool Whether or not the method is accessible from a URL. */ public function isAction($action) { $baseClass = new ReflectionClass('Cake\Controller\Controller'); if ($baseClass->hasMethod($action)) { return false; } try { $method = new ReflectionMethod($this, $action); } catch (ReflectionException $e) { return false; } return $method->isPublic(); } /** * Called before the controller action. You can use this method to configure and customize components * or perform logic that needs to happen before each controller action. * * @param \Cake\Event\Event $event An Event instance * @return \Cake\Http\Response|null * @link https://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks */ public function beforeFilter(Event $event) { return null; } /** * Called after the controller action is run, but before the view is rendered. You can use this method * to perform logic or set view variables that are required on every request. * * @param \Cake\Event\Event $event An Event instance * @return \Cake\Http\Response|null * @link https://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks */ public function beforeRender(Event $event) { return null; } /** * The beforeRedirect method is invoked when the controller's redirect method is called but before any * further action. * * If the event is stopped the controller will not continue on to redirect the request. * The $url and $status variables have same meaning as for the controller's method. * You can set the event result to response instance or modify the redirect location * using controller's response instance. * * @param \Cake\Event\Event $event An Event instance * @param string|array $url A string or array-based URL pointing to another location within the app, * or an absolute URL * @param \Cake\Http\Response $response The response object. * @return \Cake\Http\Response|null * @link https://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks */ public function beforeRedirect(Event $event, $url, Response $response) { return null; } /** * Called after the controller action is run and rendered. * * @param \Cake\Event\Event $event An Event instance * @return \Cake\Http\Response|null * @link https://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks */ public function afterFilter(Event $event) { return null; } }