跳转到内容

控制器


概览

控制器是一个包含应用程序业务逻辑的类。它还负责执行来自用户的请求。控制器中有称为动作的方法,这些方法包含业务逻辑并处理用户请求。

动作是控制器中的任何公共方法,具有Action后缀。这些动作可以通过 URL 访问,并负责解释请求和创建响应。通常,响应是以渲染视图的形式出现,但也有一些其他方式来创建响应。

Phalcon 中的控制器必须在其文件名和类名中具有Controller后缀,并且必须继承自Phalcon\Mvc\Controller类。

注意

默认控制器(当 URI 中未指定控制器时)是IndexController,默认动作(当 URL 中未指定动作时)是indexAction.

路由

[路由][routing] 在相关文档中有进一步解释。然而,默认路由是:

/:module/:controller/:action/:parameter1/:parameter2

您可以在应用程序文档中找到更多关于模块的信息。对于没有任何模块的应用程序,默认路由是:

/:controller/:action/:parameter1/:parameter2

因此,URL:

https://dev.phalcon.ld/invoices/list/2/25

将有:

Slug 描述
invoices 控制器
list 动作
2 参数1
25 参数2

上述内容将调用InvoicesControllerlistAction。参数可以通过请求在控制器和动作中获取。

控制器类可以位于应用程序的任何文件夹中,只要您的自动加载程序知道在调用时去哪里查找它们即可。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()
    {

    }
}

对于上述示例:

/invoices/list

将告诉分发器调用listAction方法并传递任何参数。但是

/invoices/other

将导致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 是:

/invoices/list/2/10
然而,您需要确保考虑到像这样的 URL:

/invoices/list/wrong-value/another-wrong-value
上述 URL 不会匹配int$pageperPage,因此会导致错误。您可能需要考虑一种策略来应对这种情况。一种方法是删除类型并确保在动作中转换参数:

<?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针对requestPhalcon\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)
            ;
        }
    }
}

假设你在afterDispatchafterExecuteRoute事件中设置了响应的状态码和内容类型,则可以始终直接返回数据。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, responseview自动注入到我们控制器中的服务。

服务作为控制器

服务可以充当控制器。控制器是总是从DI容器中请求的类。因此,任何其他以正确名称注册的类都可以轻松替换控制器:

<?php

use MyApp\Controllers\InvoicesController;
use MyApp\Components\AlternativeInvoice;

$container->set(
    InvoicesController::class,
    function () {
        return new AlternativeInvoice();
    }
);
无噪 Logo
无噪文档
25 年 6 月翻译
文档源↗