入门2应用结构

总览

Yii 应用参照模型-视图-控制器 (MVC) 设计模式来组织。 模型代表数据、业务逻辑和规则; 视图展示模型的输出;控制器 接受出入并将其转换为模型视图命令。

除了 MVC, Yii 应用还有以下部分:

入口脚本:终端用户能直接访问的 PHP 脚本, 负责启动一个请求处理周期。

应用:能全局范围内访问的对象, 管理协调组件来完成请求.

应用组件:在应用中注册的对象, 提供不同的功能来完成请求。

模块:包含完整 MVC 结构的独立包, 一个应用可以由多个模块组建。

过滤器:控制器在处理请求之前或之后 需要触发执行的代码。

小部件:可嵌入到视图中的对象, 可包含控制器逻辑,可被不同视图重复调用。

入口脚本

入口脚本是应用启动流程中的第一环, 一个应用(不管是网页应用还是控制台应用)只有一个入口脚本。 终端用户的请求通过入口脚本实例化应用并将请求转发到应用。

Web 应用的入口脚本必须放在终端用户能够访问的目录下, 通常命名为 index.php, 也可以使用 Web 服务器能定位到的其他名称。

控制台应用的入口脚本一般在应用根目录下命名为 yii(后缀为.php), 该文件需要有执行权限, 这样用户就能通过命令 ./yii <route> [arguments] [options] 来运行控制台应用。

入口脚本主要完成以下工作:

Web应用入口脚本代码

控制台应用脚本代码

定义常量 

入口脚本是定义全局常量的最好地方,Yii 支持以下三个常量:

YII_DEBUG:标识应用是否运行在调试模式。当在调试模式下,应用会保留更多日志信息, 如果抛出异常,会显示详细的错误调用堆栈。 因此,调试模式主要适合在开发阶段使用,YII_DEBUG 默认值为 false。

YII_ENV:标识应用运行的环境,详情请查阅 配置章节。 YII_ENV 默认值为 'prod',表示应用运行在线上产品环境。

YII_ENABLE_ERROR_HANDLER:标识是否启用 Yii 提供的错误处理, 默认为 true。

当定义一个常量时,通常使用类似如下代码来定义:

defined('YII_DEBUG') or define('YII_DEBUG', true);

上面的代码等同于:

if (!defined('YII_DEBUG')) {

    define('YII_DEBUG', true);

}

显然第一段代码更加简洁易懂。

常量定义应该在入口脚本的开头,这样包含其他 PHP 文件时, 常量就能生效。

应用主体

应用主体是管理 Yii 应用系统整体结构和生命周期的对象。 每个 Yii 应用系统只能包含一个应用主体,应用主体在 入口脚本 中创建并能通过表达式 \Yii::$app 全局范围内访问。

Yii有两种应用主体: 网页应用主体 和 控制台应用主体, 如名称所示,前者主要处理网页请求,后者处理控制台请求。

应用主体配置

如下所示,当 入口脚本 创建了一个应用主体, 它会加载一个 配置 文件并传给应用主体。

应用主体属性

应用主体配置文件中有许多重要的属性要配置,这些属性指定应用主体的运行环境。 比如,应用主体需要知道如何加载 控制器 , 临时文件保存到哪儿等等。 以下我们简述这些属性。

必要属性

在一个应用中,至少要配置2个属性: id 和 basePath

id 

id 属性用来区分其他应用的唯一标识ID。主要给程序使用。 为了方便协作,最好使用数字作为应用主体ID, 但不强制要求为数字。

basePath

basePath 指定该应用的根目录。 根目录包含应用系统所有受保护的源代码。 在根目录下可以看到对应 MVC 设计模式的models, viewscontrollers 等子目录。

可以使用路径或 路径别名 来在配置 basePath 属性。 两种格式所对应的目录都必须存在,否则系统会抛出一个异常。 系统会使用 realpath() 函数规范化配置的路径。

basePath 属性经常用于派生一些其他重要路径(如 runtime 路径), 因此,系统预定义 @app 代表这个路径。 派生路径可以通过这个别名组成(如@app/runtime代表runtime的路径)。

重要属性

aliases

该属性允许你用一个数组定义多个 别名。 数组的key为别名名称,值为对应的路径。 例如:

bootstrap

这个属性很实用,它允许你用数组指定启动阶段 bootstrapping process 需要运行的组件。 比如,如果你希望一个 模块 自定义 URL 规则, 你可以将模块ID加入到bootstrap数组中。

属性中的每个组件需要指定以下一项:

  • 应用 组件 ID.
  • 模块 ID.
  • 类名.
  • 配置数组.
  • 创建并返回一个组件的无名称函数.

在启动阶段,每个组件都会实例化。如果组件类实现接口 yii\base\BootstrapInterface,也会调用 bootstrap() 方法。

举一个实际的例子,Basic Application Template 应用主体配置中, 开发环境下会在启动阶段运行 debug 和 gii 模块。

components

这是最重要的属性,它允许你注册多个在其他地方使用的 应用组件。 例如

每一个应用组件指定一个key-value对的数组,key代表组件ID, value代表组件类名或 配置

在应用中可以任意注册组件,并可以通过表达式 \Yii::$app->ComponentID 全局访问。

controllerMap

该属性允许你指定一个控制器 ID 到任意控制器类。 Yii 遵循一个默认的 规则 指定控制器 ID 到任意控制器类(如 post 对应app\controllers\PostController)。 通过配置这个属性,可以打破这个默认规则,在下面的例子中, account对应到app\controllers\UserController, article 对应到 app\controllers\PostController

数组的键代表控制器ID, 数组的值代表对应的类名。

modules

该属性指定应用所包含的 模块

该属性使用数组包含多个模块类 配置,数组的键为模块ID, 例:

 

name

该属性指定你可能想展示给终端用户的应用名称, 不同于需要唯一性的 id 属性, 该属性可以不唯一,该属性用于显示应用的用途。

如果其他地方的代码没有用到,可以不配置该属性。

params

该属性为一个数组,指定可以全局访问的参数, 代替程序中硬编码的数字和字符, 应用中的参数定义到一个单独的文件并随时可以访问是一个好习惯。

runtimePath

该属性指定临时文件如日志文件、缓存文件等保存路径, 默认值为带别名的 @app/runtime

可以配置该属性为一个目录或者路径 别名, 注意应用运行时有对该路径的写入权限, 以及终端用户不能访问该路径因为临时文件可能包含一些敏感信息。

为了简化访问该路径,Yii 预定义别名 @runtime 代表该路径。

vendorPath

该属性指定 Composer 管理的供应商路径, 该路径包含应用使用的包括Yii框架在内的所有第三方库。 默认值为带别名的 @app/vendor 。

可以配置它为一个目录或者路径 别名, 当你修改时,务必修改对应的 Composer 配置。

为了简化访问该路径,Yii 预定义别名 @vendor 代表该路径。

enableCoreCommands

该属性仅 console applications 控制台应用支持, 用来指定是否启用 Yii 中的核心命令,默认值为 true

应用事件

应用在处理请求过程中会触发事件,可以在配置文件配置事件处理代码

EVENT_BEFORE_REQUEST

该事件在应用处理请求 before 之前,实际的事件名为 beforeRequest

在事件触发前,应用主体已经实例化并配置好了, 所以通过事件机制将你的代码嵌入到请求处理过程中非常不错。 例如在事件处理中根据某些参数动态设置 yii\base\Application::$language 语言属性。

EVENT_AFTER_REQUEST 

该事件在应用处理请求 after 之后但在返回响应 before 之前触发, 实际的事件名为 afterRequest

该事件触发时,请求已经被处理完, 可以做一些请求后处理或自定义响应。

注意 response 组件在发送响应给终端用户时也会触发一些事件, 这些事件都在本事件 after 之后触发。

EVENT_BEFORE_ACTION 

该事件在每个 控制器动作 运行before之前会被触发, 实际的事件名为 beforeAction.

事件的参数为一个 yii\base\ActionEvent 实例, 事件处理中可以设置yii\base\ActionEvent::$isValid 为 false 停止运行后续动作

EVENT_AFTER_ACTION

该事件在每个 控制器动作 运行 after 之后会被触发, 实际的事件名为 afterAction

该事件的参数为 yii\base\ActionEvent 实例, 通过 yii\base\ActionEvent::$result 属性, 事件处理可以访问和修改动作的结果。

应用主体生命周期

当运行 入口脚本 处理请求时, 应用主体会经历以下生命周期:

  1. 入口脚本加载应用主体配置数组。
  2. 入口脚本创建一个应用主体实例:
    • 调用 preInit() 配置几个高级别应用主体属性, 比如 basePath
    • 注册 error handler 错误处理方法。
    • 配置应用主体属性。
    • 调用 init() 初始化,该函数会调用 bootstrap() 运行引导启动组件。
  3. 入口脚本调用 yii\base\Application::run() 运行应用主体:
    • 触发 EVENT_BEFORE_REQUEST 事件。
    • 处理请求:解析请求 路由 和相关参数; 创建路由指定的模块、控制器和动作对应的类,并运行动作。
    • 触发 EVENT_AFTER_REQUEST 事件。
    • 发送响应到终端用户。
  4. 入口脚本接收应用主体传来的退出状态并完成请求的处理。

应用组件

应用主体是服务定位器, 它部署一组提供各种不同功能的 应用组件 来处理请求。 例如,urlManager组件负责处理网页请求路由到对应的控制器。 db组件提供数据库相关服务等等。

例如,可以使用 \Yii::$app->db 来获取到已注册到应用的 DB connection

第一次使用以上表达式时候会创建应用组件实例, 后续再访问会返回此实例,无需再次创建。

应用组件可以是任意对象,可以在 应用主体配置配置 yii\base\Application::$components 属性。 例如:

引导启动组件

上面提到一个应用组件只会在第一次访问时实例化, 如果处理请求过程没有访问的话就不实例化。 有时你想在每个请求处理过程都实例化某个组件即便它不会被访问, 可以将该组件ID加入到应用主体的 bootstrap 属性中。

核心应用组件

Yii 定义了一组固定ID和默认配置的 核心 组件, 例如 request 组件 用来收集用户请求并解析 路由; db 代表一个可以执行数据库操作的数据库连接。 通过这些组件,Yii应用主体能处理用户请求。

控制器

控制器是 MVC 模式中的一部分, 是继承yii\base\Controller类的对象,负责处理请求和生成响应。 具体来说,控制器从应用主体 接管控制后会分析请求数据并传送到模型, 传送模型结果到视图,最后生成输出响应信息。

动作

控制器由 操作 组成,它是执行终端用户请求的最基础的单元, 一个控制器可有一个或多个操作。

如下示例显示包含两个动作view and create 的控制器post

路由

终端用户通过所谓的路由寻找到动作,路由是包含以下部分的字符串:

  • 模块ID: 仅存在于控制器属于非应用的模块;
  • 控制器ID: 同应用(或同模块如果为模块下的控制器) 下唯一标识控制器的字符串;
  • 操作ID: 同控制器下唯一标识操作的字符串。

路由使用如下格式:

ControllerID/ActionID

如果属于模块下的控制器,使用如下格式:

ModuleID/ControllerID/ActionID

如果用户的请求地址为 http://hostname/index.php?r=site/index, 会执行site 控制器的index 操作。

控制器ID

通常情况下,控制器用来处理请求有关的资源类型, 因此控制器ID通常为和资源有关的名词。 

控制器部署

可通过配置 controller map 来强制上述的控制器ID和类名对应, 通常用在使用第三方不能掌控类名的控制器上。

默认控制器

每个应用有一个由yii\base\Application::$defaultRoute属性指定的默认控制器; 当请求没有指定 路由,该属性值作为路由使用。 对于Web applications网页应用,它的值为 'site',对于 console applications 控制台应用,它的值为 help,所以URL为 http://hostname/index.php 表示由 site 控制器来处理。

创建动作

创建操作可简单地在控制器类中定义所谓的 操作方法 来完成,操作方法必须是以action开头的公有方法。

内联动作

内联动作指的是根据我们刚描述的操作方法。

动作方法的名字是根据操作ID遵循如下规则衍生:

  1. 将每个单词的第一个字母转为大写;
  2. 去掉中横杠;
  3. 增加action前缀.

例如index 转成 actionIndexhello-world 转成 actionHelloWorld

独立动作

独立操作通过继承yii\base\Action或它的子类来定义。 例如Yii发布的yii\web\ViewAction 和yii\web\ErrorAction都是独立操作。

要使用独立操作,需要通过控制器中覆盖yii\base\Controller::actions()方法在action map中申明, 如下例所示:

如上所示, actions() 方法返回键为操作ID、值为对应操作类名 或数组configurations 的数组。 和内联操作不同,独立操作ID可包含任意字符,只要在actions() 方法中申明.

为创建一个独立操作类,需要继承yii\base\Action 或它的子类,并实现公有的名称为run()的方法, run() 方法的角色和操作方法类似,例如:

动作结果

操作方法或独立操作的run()方法的返回值非常重要, 它表示对应操作结果。

返回值可为 响应 对象,作为响应发送给终端用户。

默认动作

每个控制器都有一个由 yii\base\Controller::$defaultAction 属性指定的默认操作, 当路由 只包含控制器ID, 会使用所请求的控制器的默认操作。

默认操作默认为 index,如果想修改默认操作,只需简单地在控制器类中覆盖这个属性, 如下所示:

控制器生命周期

处理一个请求时,应用主体 会根据请求 路由创建一个控制器, 控制器经过以下生命周期来完成请求:

  1. 在控制器创建和配置后,yii\base\Controller::init() 方法会被调用。
  2. 控制器根据请求操作ID创建一个操作对象:
  3. 控制器按顺序调用应用主体、模块(如果控制器属于模块)、 控制器的 beforeAction() 方法;
    • 如果任意一个调用返回false,后面未调用的beforeAction()会跳过并且操作执行会被取消; action execution will be cancelled.
    • 默认情况下每个 beforeAction() 方法会触发一个 beforeAction 事件,在事件中你可以追加事件处理操作;
  4. 控制器执行操作:
    • 请求数据解析和填入到操作参数;
  5. 控制器按顺序调用控制器、模块(如果控制器属于模块)、应用主体的 afterAction() 方法;
    • 默认情况下每个 afterAction() 方法会触发一个 afterAction 事件, 在事件中你可以追加事件处理操作;
  6. 应用主体获取操作结果并赋值给响应.

模型

模型是 MVC 模式中的一部分, 是代表业务数据、规则和逻辑的对象。

可通过继承 yii\base\Model 或它的子类定义模型类, 基类yii\base\Model支持许多实用的特性:

  • 属性: 代表可像普通类属性或数组 一样被访问的业务数据;
  • 属性标签: 指定属性显示出来的标签;
  • 块赋值: 支持一步给许多属性赋值;
  • 验证规则: 确保输入数据符合所申明的验证规则;
  • 数据导出: 允许模型数据导出为自定义格式的数组。

属性

模型通过 属性 来代表业务数据,每个属性像是模型的公有可访问属性, yii\base\Model::attributes() 指定模型所拥有的属性。

模型通过 属性 来代表业务数据,每个属性像是模型的公有可访问属性, yii\base\Model::attributes() 指定模型所拥有的属性。

可像访问一个对象属性一样访问模型的属性:

也可像访问数组单元项一样访问属性,这要感谢yii\base\Model支持 ArrayAccess 数组访问 和 ArrayIterator 数组迭代器:

场景

模型可能在多个 场景 下使用,例如 User 模块可能会在收集用户登录输入, 也可能会在用户注册时使用。在不同的场景下, 模型可能会使用不同的业务规则和逻辑, 例如 email 属性在注册时强制要求有,但在登陆时不需要。

验证规则

当模型接收到终端用户输入的数据, 数据应当满足某种规则(称为 验证规则, 也称为 业务规则)。

例如OA系统中所有报销单的发票校验组件,如果是代开饭票,不需要判断是否有销售方信息

我这里分为普通场景以及代开饭票场景,在代开的时候不开启校验

如果没有指定 on 属性,规则会在所有场景下应用, 在当前scenario 下应用的规则称之为 active rule活动规则

有时你想一条规则只在某个 场景 下应用,为此你可以指定规则的 on 属性

块赋值

块赋值只用一行代码将用户所有输入填充到一个模型,非常方便, 它直接将输入数据对应填充到 yii\base\Model::attributes() 属性。 以下两段代码效果是相同的, 都是将终端用户输入的表单数据赋值到 ContactForm 模型的属性, 明显地前一段块赋值的代码比后一段代码简洁且不易出错。

 

安全属性

块赋值只应用在模型当前scenario 场景yii\base\Model::scenarios()方法 列出的称之为 安全属性 的属性上,例如,如果User模型申明以下场景, 当当前场景为login时候,只有username and password 可被块赋值, 其他属性不会被赋值。

safe 的验证器来申明 哪些属性是安全的不需要被验证, 如下示例的规则申明 title 和 description 都为安全属性。

非安全属性

如上所述,yii\base\Model::scenarios() 方法提供两个用处:定义哪些属性应被验证,定义哪些属性安全。 在某些情况下,你可能想验证一个属性但不想让他是安全的, 可在scenarios()方法中属性名加一个惊叹号 !。 例如像如下的secret属性。

数据导出

模型通常要导出成不同格式,例如,你可能想将模型的一个集合转成JSON或Excel格式, 导出过程可分解为两个步骤:

  • 模型转换成数组;
  • 数组转换成所需要的格式。

你只需要关注第一步,因为第二步可被通用的 数据转换器如yii\web\JsonResponseFormatter来完成。

将模型转换为数组最简单的方式是使用 yii\base\Model::attributes() 属性, 例如:

 

字段

字段是模型通过调用yii\base\Model::toArray() 生成的数组的单元名。

默认情况下,字段名对应属性名,但是你可以通过覆盖 fields() 和/或 extraFields() 方法来改变这种行为, 两个方法都返回一个字段定义列表,fields() 方法定义的字段是默认字段, 表示toArray()方法默认会返回这些字段。 extraFields()方法定义额外可用字段, 通过toArray()方法指定$expand参数来返回这些额外可用字段。 例如如下代码会返回fields()方法定义的所有字段和extraFields()方法定义的prettyName and fullAddress字段。

$array = $model->toArray([], ['prettyName', 'fullAddress']);

可通过覆盖 fields() 来增加、删除、重命名和重定义字段, fields() 方法返回值应为数组, 数组的键为字段名,数组的值为对应的可为属性名或匿名函数返回的字段定义对应的值。 特使情况下,如果字段名和属性定义名相同,可以省略数组键, 例如:

视图

视图是 MVC 模式中的一部分。 它是展示数据到终端用户的代码,在网页应用中, 根据视图模板来创建视图,视图模板为PHP脚本文件, 主要包含HTML代码和展示类PHP代码,通过view应用组件来管理, 该组件主要提供通用方法帮助视图构造和渲染, 简单起见,我们称视图模板或视图模板文件为视图。

模块

模块是独立的软件单元,由模型视图, 控制器和其他支持组件组成, 终端用户可以访问在应用主体中已安装的模块的控制器, 模块被当成小应用主体来看待,和应用主体不同的是, 模块不能单独部署,必须属于某个应用主体。

创建模块

模块被组织成一个称为 base path 的目录, 在该目录中有子目录如 controllersmodelsviews 分别为对应控制器,模型,视图和其他代码,和应用非常类似。

过滤器

过滤器是 控制器动作 执行之前或之后执行的对象。 例如访问控制过滤器可在动作执行之前来控制特殊终端用户是否有权限执行动作, 内容压缩过滤器可在动作执行之后发给终端用户之前压缩响应内容。

过滤器可包含预过滤(过滤逻辑在动作之前)或后过滤(过滤逻辑在动作之后), 也可同时包含两者。

使用过滤器

过滤器本质上是一类特殊的 行为, 所以使用过滤器和 使用行为一样。 可以在控制器类中覆盖它的 behaviors() 方法来声明过滤器,如下所示:

 

控制器类的过滤器默认应用到该类的 所有 动作, 你可以配置 only 属性明确指定控制器应用到哪些动作。 在上述例子中,HttpCache 过滤器只应用到 index 和 view 动作。 也可以配置 except 属性 使一些动作不执行过滤器。

除了控制器外,可在 模块应用主体 中申明过滤器。 申明之后,过滤器会应用到所属该模块或应用主体的 所有 控制器动作, 除非像上述一样配置过滤器的 only 和 except 属性。

创建过滤器

继承 yii\base\ActionFilter 类并覆盖 beforeAction() 或 afterAction() 方法来创建动作的过滤器,前者在动作执行之前执行,后者在动作执行之后执行。 beforeAction() 返回值决定动作是否应该执行, 如果为 false,之后的过滤器和动作不会继续执行。

下面的例子申明一个记录动作执行时间日志的过滤器。

核心过滤器

Yii 提供了一组常用过滤器,在 yii\filters 命名空间下, 接下来我们简要介绍这些过滤器。

AccessControl

AccessControl 提供基于 rules 规则的访问控制。 特别是在动作执行之前,访问控制会检测所有规则 并找到第一个符合上下文的变量(比如用户 IP 地址、登录状态等等)的规则, 来决定允许还是拒绝请求动作的执行, 如果没有规则符合,访问就会被拒绝。

如下示例表示表示允许已认证用户访问 create 和 update 动作, 拒绝其他用户访问这两个动作。

认证方法过滤器

认证方法过滤器通过 HTTP Basic Auth 或 OAuth 2 来认证一个用户,认证方法过滤器类在 yii\filters\auth 命名空间下。

如下示例表示可使用 yii\filters\auth\HttpBasicAuth 来认证一个用户, 它使用基于 HTTP 基础认证方法的令牌。 注意为了可运行,user identity class 类必须 实现 findIdentityByAccessToken() 方法。

ContentNegotiator

ContentNegotiator 支持响应内容格式处理和语言处理。 通过检查 GET 参数和 Accept HTTP 头部来决定响应内容格式和语言。

HttpCache

HttpCache 利用 Last-Modified 和 Etag HTTP 头实现客户端缓存。

PageCache

PageCache 实现服务器端整个页面的缓存

RateLimiter 

RateLimiter 根据 漏桶算法 来实现速率限制。 

VerbFilter

VerbFilter 检查请求动作的 HTTP 请求方式是否允许执行, 如果不允许,会抛出 HTTP 405异常。

Cors

跨域资源共享 CORS 机制允许一个网页的许多资源(例如字体、JavaScript等) 这些资源可以通过其他域名访问获取。 特别是 JavaScript 的 AJAX 调用可使用 XMLHttpRequest 机制, 由于同源安全策略该跨域请求会被网页浏览器禁止。CORS 定义浏览器和服务器交互时哪些跨域请求允许和禁止。

Cors filter 应在授权/认证过滤器之前定义, 以保证 CORS 头部被发送。

CROS过滤器可以通过 $cors 属性进行调整。

  • cors['Origin']:定义允许来源的数组,可为 ['*'](任何用户)或 ['http://www.myserver.net', 'http://www.myotherserver.com']。 默认为 ['*']
  • cors['Access-Control-Request-Method']:允许动作数组如 ['GET', 'OPTIONS', 'HEAD']。默认为 ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
  • cors['Access-Control-Request-Headers']:允许请求头部数组,可为 ['*'] 所有类型头部 或 ['X-Request-With'] 指定类型头部。默认为 ['*']
  • cors['Access-Control-Allow-Credentials']:定义当前请求是否使用证书,可为 truefalse 或 null(不设置)。默认为 null
  • cors['Access-Control-Max-Age']: 定义请求的有效时间,默认为 86400

可以覆盖默认参数为每个动作调整 CORS 头部。例如,为 login 动作 增加 Access-Control-Allow-Credentials 参数如下所示:

小部件

小部件是在视图中使用的可重用单元, 使用面向对象方式创建复杂和可配置用户界面单元。

资源

Yii 中的资源是和 Web 页面相关的文件,可为 CSS 文件,JavaScript 文件,图片或视频等, 资源放在 Web 可访问的目录下,直接被 Web 服务器调用。

通过程序自动管理资源更好一点,例如,当你在页面中使用 yii\jui\DatePicker 小部件时, 它会自动包含需要的 CSS 和 JavaScript 文件, 而不是要求你手工去找到这些文件并包含, 当你升级小部件时,它会自动使用新版本的资源文件

扩展

扩展是专门设计的在 Yii 应用中随时可拿来使用的, 并可重发布的软件包。例如, yiisoft/yii2-debug 扩展在你的应用的每个页面底部添加一个方便用于调试的工具栏, 帮助你简单地抓取页面生成的情况。 

posted @ 2021-08-12 19:23  Adom_ye  阅读(43)  评论(0编辑  收藏  举报