控制器¶
概览¶
控制器是一个包含应用程序业务逻辑的类。它还负责执行来自用户的请求。控制器中有称为动作的方法,这些方法包含业务逻辑并处理用户请求。
动作是控制器中的任何公共方法,具有Action
后缀。这些动作可以通过 URL 访问,并负责解释请求和创建响应。通常,响应是以渲染视图的形式出现,但也有一些其他方式来创建响应。
Phalcon 中的控制器必须在其文件名和类名中具有Controller
后缀,并且必须继承自Phalcon\Mvc\Controller类。
注意
默认控制器(当 URI 中未指定控制器时)是IndexController,默认动作(当 URL 中未指定动作时)是indexAction.
路由¶
[路由][routing] 在相关文档中有进一步解释。然而,默认路由是:
您可以在应用程序文档中找到更多关于模块的信息。对于没有任何模块的应用程序,默认路由是:
因此,URL:
将有:
Slug | 描述 |
---|---|
invoices | 控制器 |
list | 动作 |
2 | 参数1 |
25 | 参数2 |
上述内容将调用InvoicesController
和listAction
。参数可以通过请求在控制器和动作中获取。
控制器类可以位于应用程序的任何文件夹中,只要您的自动加载程序知道在调用时去哪里查找它们即可。Phalcon\Autoload\Loader具有多种选项,用于注册目录、命名空间等,以帮助发现控制器。
一个示例控制器如下:
<?php
use Phalcon\Mvc\Controller;
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction(int $page = 1, int $perPage = 25)
{
}
}
初始化¶
Phalcon\Mvc\Controller调用initialize()
方法(如果存在),在控制器上执行任何动作之前。
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Tag;
/**
* @property Tag $tag
*/
class InvoicesController extends Controller
{
public function initialize()
{
$this->tag->title()->set('Invoices Management');
}
public function listAction(int $page = 1, int $perPage = 25)
{
}
}
注意
使用__construct()
方法不推荐。
注意
The initialize()
方法仅在beforeExecuteRoute
事件成功执行时被调用。这是为了确保如果在事件中有授权检查代码,initialize
将永远不会被调用。
如果您想在控制器对象构造后立即执行一些初始化逻辑,则可以实现onConstruct()
方法:
<?php
use Phalcon\Mvc\Controller;
class InvoicesController extends Controller
{
public function onConstruct()
{
// ...
}
}
注意
注意onConstruct()
即使要在控制器中执行的动作不存在或用户无法访问它(假设应用程序中实现了自定义访问控制),也会执行。
分发循环¶
分发循环将在分派器内执行,直到没有需要执行的动作为止。在上述示例中,我们仅展示了单个动作中的代码,该代码将与适当的请求一起执行。
我们可以利用分派器对象将请求转发到不同的模块、控制器或动作,从而在分发循环中创建更复杂的操作流程。
<?php
use Phalcon\Dispatcher;
use Phalcon\Flash\Direct;
use Phalcon\Mvc\Controller;
/**
* @property Dispatcher $dispatcher
* @property Direct $flash
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function showAction($year, $postTitle)
{
$this->flash->error(
"You do not have permission to access this area"
);
// Forward flow to another action
$this->dispatcher->forward(
[
'controller' => 'users',
'action' => 'login',
]
);
}
}
如果用户没有权限访问特定动作,他们将被转发到login
动作在UsersController
.
<?php
use Phalcon\Mvc\Controller;
class UsersController extends Controller
{
public function indexAction()
{
}
public function loginAction()
{
}
}
上述是一个简单的转发示例,适用于未登录或无访问权限的用户。您可以查看下面的事件部分,了解如何利用事件为整个应用程序全局实现相同的功能。
在您的应用程序中,forward
调用次数没有限制。但是,您需要注意,因为转发可能导致循环引用,此时您的应用程序将停止运行。如果没有其他动作由分发循环分发,分发器将自动调用由 [Phalcon\Mvc\View][views] 管理的 MVC 视图层。
动作¶
动作是调用以执行我们应用程序所需功能的方法。动作必须必须以后缀Action
结尾,它们匹配用户发出的路由请求。
<?php
use Phalcon\Mvc\Controller;
class InvoicesController extends Controller
{
public function listAction(int $page = 1, int $perPage = 25)
{
}
public function other()
{
}
}
对于上述示例:
将告诉分发器调用listAction
方法并传递任何参数。但是
将导致404
- 页面未找到。
参数¶
额外的 URI 参数被定义为动作参数,以便可以使用局部变量轻松访问。控制器可以选择继承Phalcon\Mvc\Controller。通过这样做,控制器可以方便地访问应用程序服务。
没有默认值的参数被视为必需项。设置参数的可选值的方式与 PHP 中相同:
<?php
use Phalcon\Mvc\Controller;
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction(int $page = 1, int $perPage = 25)
{
}
}
注意
您需要添加额外的代码以确保传递的数据是正确的类型,并要么使用默认值,要么使用正确的值。否则,您将遇到错误。
对于上述示例,调用该方法的 URL 是:
然而,您需要确保考虑到像这样的 URL: 上述 URL 不会匹配int
的$page
或perPage
,因此会导致错误。您可能需要考虑一种策略来应对这种情况。一种方法是删除类型并确保在动作中转换参数: <?php
use Phalcon\Mvc\Controller;
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction($page = 1, $perPage = 25)
{
$page = (int) $page;
$perPage = (int) $perPage;
}
}
您还可以从动作声明中删除参数,而是从分发器中检索它们。参数按照它们在路由中传递的顺序分配。您可以按名称获取参数如下:
<?php
use Phalcon\Dispatcher;
use Phalcon\Mvc\Controller;
/**
* @property Dispatcher $dispatcher
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction()
{
$year = $this->dispatcher->getParam('year');
$postTitle = $this->dispatcher->getParam('postTitle');
}
}
上述参数将按定义的路由匹配。
事件¶
控制器自动充当分发器 事件的监听器,通过实现这些事件名称的方法,允许您在执行动作之前/之后实现钩子点:
<?php
use Phalcon\Dispatcher;
use Phalcon\Flash\Direct;
use Phalcon\Mvc\Controller;
/**
* @property Dispatcher $dispatcher
* @property Direct $flash
*/
class InvoicesController extends Controller
{
public function beforeExecuteRoute($dispatcher)
{
// This is executed before every found action
if ($dispatcher->getActionName() === 'save') {
$this->flash->error(
"You do not have permission to save invoices"
);
$this->dispatcher->forward(
[
'controller' => 'home',
'action' => 'index',
]
);
return false;
}
}
public function afterExecuteRoute($dispatcher)
{
// Executed after every found action
}
}
请求 - 响应¶
如果你已经注册了请求和响应服务到你的DI容器,或者只是实例化了Phalcon\Di\FactoryDefault其中一个,你可以在控制器中将这些对象作为属性访问。
对于Phalcon\Di\FactoryDefault,你的对象将会是Phalcon\Http\Request针对request
和Phalcon\Http\Response响应。该request
包含用户请求的内容,包括由使用的方法设置的所有变量 (GET
, POST
等等)以及有关请求的其他信息。该response
包含我们需要返回的数据,例如content-type
状态码、负载等等。
注意
为了从你的控制器中访问服务,你需要扩展Phalcon\Mvc\Controller
类的组件(包括控制器)
<?php
use Phalcon\Http\Request;
use Phalcon\Mvc\Controller;
/**
* @property Request $request
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction()
{
if (true === $this->request->isPost()) {
$page = $this
->request
->getPost('page', 'int', 1)
;
$perPage = $this
->request
->getPost('perPage', 'int', 25)
;
}
}
}
上述代码首先检查请求是否为POST
请求。如果是,则从$_POST
超全局变量中获取两个变量。我们使用的语法是:- 获取变量 (page
) - 如果存在,将其清理为整数 - 如果不存在,返回默认值1
使用此技术,我们确保所有输入都经过适当清理并设置了默认值。
在大多数情况下,响应对象不会直接调用,而是逐步构建或附加到afterDispatch
事件。例如,如果需要通过AJAX请求向用户发送JSON数据,我们可以直接在操作中与响应进行交互:
<?php
use Phalcon\Http\Request;
use Phalcon\Http\Response;
use Phalcon\Mvc\Controller;
/**
* @property Request $request
* @property Response $response
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction()
{
if (true === $this->request->isPost()) {
$page = $this
->request
->getPost('page', 'int', 1)
;
$perPage = $this
->request
->getPost('perPage', 'int', 25)
;
// ......
$data = $records->toArray();
$this
->response
->setStatusCode(200, 'OK')
->setJsonContent($data)
;
}
}
}
假设你在afterDispatch
或afterExecuteRoute
事件中设置了响应的状态码和内容类型,则可以始终直接返回数据。Phalcon会将其设置为返回的有效负载。这在编写API时特别有用。
<?php
use Phalcon\Http\Request;
use Phalcon\Http\Response;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\View;
/**
* @property Request $request
* @property Response $response
* @property View $view
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction()
{
if (true === $this->request->isPost()) {
$page = $this
->request
->getPost('page', 'int', 1)
;
$perPage = $this
->request
->getPost('perPage', 'int', 25)
;
// ......
return $records->toArray();
}
}
public function afterExecuteRoute($dispatcher)
{
$this->view->disable();
$this->response->setContentType('application/json', 'UTF-8');
$this->response->setHeader('Cache-Control', 'no-store');
/** @var array $data */
$data = $dispatcher->getReturnedValue();
$dispatcher->setReturnedValue([]);
if (true !== $this->response->isSent()) {
$this->response->setJsonContent($data);
return $this->response->send();
}
}
}
在上述示例中,我们从操作中返回一个数组。afterExecuteRoute
方法禁用视图,将内容类型设置为JSON,然后如果响应尚未发送,则设置JSON内容并发送响应。
会话¶
会话帮助我们维持请求之间的持久数据。你可以使用属性Phalcon\Session\Bag从任何控制器访问persistent
以封装需要持久化的数据:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Session\Bag;
/**
* @property Bag $persistent
*/
class UserController extends Controller
{
public function indexAction()
{
$this->persistent->name = 'Darth';
}
public function welcomeAction()
{
echo 'Welcome, ', $this->persistent->name;
}
}
注意
注意,persistent
服务会自动注册到任何扩展Phalcon\Di\Injectable
类的组件(包括控制器)
依赖注入¶
你可以创建一个作为独立类的控制器。然而,你可以扩展Phalcon\Mvc\Controller类,它将整个DI容器暴露给你。每个服务都可以通过其名称作为控制器的属性使用:
<?php
use Phalcon\Http\Request;
use Phalcon\Http\Response;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\View;
/**
* @property Request $request
* @property Response $response
* @property View $view
*/
class InvoicesController extends Controller
{
public function indexAction()
{
}
public function listAction()
{
if (true === $this->request->isPost()) {
$page = $this
->request
->getPost('page', 'int', 1)
;
$perPage = $this
->request
->getPost('perPage', 'int', 25)
;
// ......
return $records->toArray();
}
}
public function afterExecuteRoute($dispatcher)
{
$this->view->disable();
$this->response->setContentType('application/json', 'UTF-8');
$this->response->setHeader('Cache-Control', 'no-store');
/** @var array $data */
$data = $dispatcher->getReturnedValue();
$dispatcher->setReturnedValue([]);
if (true !== $this->response->isSent()) {
$this->response->setJsonContent($data);
return $this->response->send();
}
}
}
在上述示例中,我们访问了request
, response
和view
自动注入到我们控制器中的服务。
服务作为控制器¶
服务可以充当控制器。控制器是总是从DI容器中请求的类。因此,任何其他以正确名称注册的类都可以轻松替换控制器: