入口脚本
入口脚本主要完成以下工作
- 定义全局常量;
- 注册 Composer 自动加载器;
- 包含 Yii 类文件;
- 加载应用配置;
- 创建一个应用实例并配置;
- 调用 yii\base\Application::run() 来处理请求。
run();
应用
1、应用主题
Application
$app = (new yii\web\Application($config));
2、应用主体配置
$config = require __DIR__ . '/../config/web.php';
3、应用主体属性
config/web.php
'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'aliases' => [
'@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset',
],
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => 'oG32SGLA71IQQdd50xmTNWMNyPEdiVHR',
],
'cache' => [
'class' => 'yii\caching\FileCache',
],
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
'errorHandler' => [
'errorAction' => 'site/error',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
// send all mails to a file by default. You have to set
// 'useFileTransport' to false and configure a transport
// for the mailer to send real emails.
'useFileTransport' => true,
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'db' => $db,
/*
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
],
],
*/
],
'params' => $params,
];
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yii\debug\Module',
// uncomment the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
// uncomment the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
];
}
return $config;
4、生命周期
当运行 入口脚本 处理请求时, 应用主体会经历以下生命周期:
入口脚本加载应用主体配置数组。
入口脚本创建一个应用主体实例:
- 调用 preInit() 配置几个高级别应用主体属性, 比如 basePath。
- 注册 error handler 错误处理方法。
- 配置应用主体属性。
- 调用 init() 初始化,该函数会调用 bootstrap() 运行引导启动组件。
入口脚本调用
yii\base\Application::run()
运行应用主体:
- 触发 EVENT_BEFORE_REQUEST 事件。
- 处理请求:解析请求 路由 和相关参数; 创建路由指定的模块、控制器和动作对应的类,并运行动作。
- 触发 EVENT_AFTER_REQUEST 事件。
- 发送响应到终端用户。
- 入口脚本接收应用主体传来的退出状态并完成请求的处理。
basic\vendor\yiisoft\yii2\base\Application.php
public function __construct($config = [])
{
//入口脚本创建一个应用主体实例:
Yii::$app = $this;
static::setInstance($this);
$this->state = self::STATE_BEGIN;
//调用 preInit()配置几个高级别应用主体属性, 比如 basePath
$this->preInit($config);
//注册 error handler错误处理方法
$this->registerErrorHandler($config);
//配置应用主体属性。
Component::__construct($config);
}
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
basic\vendor\yiisoft\yii2\base\BaseObject.php
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
//调用init() 初始化,该函数会调用 bootstrap()运行引导启动组件。
$this->init();//这里调用base\Application.php的init
}
应用组件
可以使用 \Yii::$app->db
来获取到已注册到应用的 DB connection, 使用 \Yii::$app->cache
来获取到已注册到应用的 primary cache。
第一次使用以上表达式时候会创建应用组件实例, 后续再访问会返回此实例,无需再次创建。
应用组件可以是任意对象,可以在 应用主体配置配置 yii\base\Application::$components 属性。
[
'components' => [
// 使用类名注册 "cache" 组件
'cache' => 'yii\caching\ApcCache',
// 使用配置数组注册 "db" 组件
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
],
// 使用函数注册"search" 组件
'search' => function () {
return new app\components\SolrService;
},
],
]
控制器
控制器是 MVC
模式中的一部分, 是继承yii\base\Controller
类的对象,负责处理请求和生成响应。 具体来说,控制器从应用主体 接管控制后会分析请求数据并传送到模型, 传送模型结果到视图,最后生成输出响应信息。
控制器Id可包含子目录前缀,例如 sub/test
代表 controller namespace 控制器命名空间下 sub
子目录中 test
控制器。 子目录前缀可为英文大小写字母、数字、下划线、正斜杠,其中正斜杠用来区分多级子目录(如
panels/admin`)。
独立动作
暂时不知道有啥用?
动作参数
string(1) "1" }
public function actionView(array $id, $message)
{
var_dump($id,$message);
}
}
默认动作
每个控制器都有一个由 yii\base\Controller::$defaultAction
属性指定的默认操作, 当路由只包含控制器ID, 会使用所请求的控制器的默认操作。
控制器的生命周期
处理一个请求时,应用主体 会根据请求 路由创建一个控制器, 控制器经过以下生命周期来完成请求:
在控制器创建和配置后,yii\base\Controller::init() 方法会被调用。
控制器根据请求操作ID创建一个操作对象:
- 如果操作ID没有指定,会使用default action ID默认操作ID;
- 如果在action map找到操作ID, 会创建一个独立操作;
- 如果操作ID对应操作方法,会创建一个内联操作;
- 否则会抛出yii\base\InvalidRouteException异常。
- 控制器按顺序调用应用主体、模块(如果控制器属于模块)、 控制器的
beforeAction()
方法;
- 如果任意一个调用返回false,后面未调用的
beforeAction()
会跳过并且操作执行会被取消; action execution will be cancelled.- 默认情况下每个
beforeAction()
方法会触发一个beforeAction
事件,在事件中你可以追加事件处理操作;
- 控制器执行操作:
- 请求数据解析和填入到操作参数;
- 控制器按顺序调用控制器、模块(如果控制器属于模块)、应用主体的
afterAction()
方法;
- 默认情况下每个
afterAction()
方法会触发一个afterAction
事件, 在事件中你可以追加事件处理操作;
- 应用主体获取操作结果并赋值给响应.
模型
- 属性
可以像访问对象属性一样访问模型属性
也可以像访问数组下标一样访问模型的属性
$model = new \app\models\ContactForm;
// "name" 是ContactForm模型的属性
$model->name = 'example';
echo $model->name;
// 像访问数组单元项一样访问属性
$model['name'] = 'example';
echo $model['name'];
- 定义属性
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
- 属性标签
默认情况下,属性标签通过yii\base\Model::generateAttributeLabel()方法自动从属性名生成. 它会自动将驼峰式大小写变量名转换为多个首字母大写的单词, 例如
username
转换为Username
,firstName
转换为First Name
。
echo $model->getAttributeLabel('verifyCode');
//以上代码输出为
//Verify Code
//你也可以在当前模型中自定义标签,重写该方法即可
public function attributeLabels()
{
return [
'verifyCode' => 'Verification Code',
];
}
//这样再次执行 echo $model->getAttributeLabel('verifyCode')
//输出将会是
//Verification Code
- 验证规则
$model = new \app\models\ContactForm;
// 用户输入数据赋值到模型属性
$model->attributes = \Yii::$app->request->post();
if ($model->validate()) {
// 所有输入数据都有效 all inputs are valid
} else {
// 验证失败:$errors 是一个包含错误信息的数组
$errors = $model->errors;
var_dump($errors);die;
}
- 块赋值
块赋值只用一行代码将用户所有输入填充到一个模型,非常方便, 它直接将输入数据对应填充到 yii\base\Model::attributes()
属性
$model->attributes = \Yii::$app->request->post();
- 安全属性
- 非安全属性
- 数据导出
- 字段
视图
1、创建视图
= $form->field($model, 'name') ?>
= $form->field($model, 'email') ?>
= $form->field($model, 'age') ?>
= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
2、安全
当创建生成HTML页面的视图时,在显示之前将用户输入数据进行转码和过滤非常重要, 否则,你的应用可能会被 跨站脚本 攻击。
使用yii\helpers\Html::encode()
进行转码
= Html::encode($message) ?>
3、渲染视图
在控制器中 app\controllers\PostController
调用 $this->render('view')
, 实际上渲染 @app/views/post/view.php
视图文件,当在该视图文件中调用 $this->render('_overview')
会渲染 @app/views/post/_overview.php
视图文件。
4、视图中访问数据
推送和拉取
推送:通过render等渲染视图方法的第二个参数传递数据
拉取:拉取方式可让视图从view component
视图组件或其他对象中主动获得数据(如Yii::$app
), 在视图中使用如下表达式$this->context可获取到控制器ID, 可让你在report视图中获取控制器的任意属性或方法, 如以下代码获取控制器ID。
= $this->context->id ?> //输出当前控制器ID
= var_dump($this->context->behaviors());echo '
';?>
= Html::encode($message) ?>
5、布局
布局是一种特殊的视图,代表多个视图的公共部分, 例如,大多数Web应用共享相同的页头和页尾, 在每个视图中重复相同的页头和页尾,更好的方式是将这些公共放到一个布局中, 渲染内容视图后在合适的地方嵌入到布局中。
可配置yii\base\Application::$layout
或 yii\base\Controller::$layout
使用其他布局文件, 前者管理所有控制器的布局,后者覆盖前者来控制单个控制器布局。 例如,如下代码使 post
控制器渲染视图时使用 @app/views/layouts/post.php
作为布局文件, 假如 layout
属性没改变, 控制器默认使用 @app/views/layouts/main.php
作为布局文件。
namespace app\controllers;
use yii\web\Controller;
class PostController extends Controller
{
public $layout = 'post';
// ...
}
嵌套布局????
设置页面标题
title = 'My page title';
?>
= Html::encode($this->title) ?>
注册Meta标签
registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']);
?>
注册链接标签
$this->registerLinkTag([
'title' => 'Live News for Yii',
'rel' => 'alternate',
'type' => 'application/rss+xml',
'href' => 'http://www.yiiframework.com/rss.xml/',
]);
//上述代码会转为
6、渲染静态页面
如果Web
站点包含很多静态页面,多次重复相似的代码显得很繁琐, 为解决这个问题,可以使用一个在控制器中称为 yii\web\ViewAction
的独立动作
如:
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
//独立动作 如果
public function actions()
{
return [
'page' => [
'class' => 'yii\web\ViewAction',
],
];
}
}
如果在在@app/views/site/pages
目录下创建名为about
的视图,可以通过以下url来显示
index.php?r=site/page&view=index
GET 中 view 参数告知 yii\web\ViewAction 动作请求哪个视图,然后操作在 @app/views/site/pages
目录下寻找该视图。可配置 yii\web\ViewAction::$viewPrefix
修改搜索视图的目录,默认是pages
。
最佳实践 ¶
视图负责将模型的数据展示用户想要的格式,总之,视图
- 应主要包含展示代码,如HTML, 和简单的PHP代码来控制、格式化和渲染数据;
- 不应包含执行数据查询代码,这种代码放在模型中;
- 应避免直接访问请求数据,如
$_GET
,$_POST
,这种应在控制器中执行, 如果需要请求数据,应由控制器推送到视图。- 可读取模型属性,但不应修改它们。
为使模型更易于维护,避免创建太复杂或包含太多冗余代码的视图, 可遵循以下方法达到这个目标:
- 使用 布局 来展示公共代码(如,页面头部、尾部);
- 将复杂的视图分成几个小视图,可使用上面描述的渲染方法将这些 小视图渲染并组装成大视图;
- 创建并使用 小部件 作为视图的数据块;
- 创建并使用助手类在视图中转换和格式化数据。
模块 Modules
模块是独立的软件单元,由模型,视图, 控制器和其他支持组件组成, 终端用户可以访问在应用主体中已安装的模块的控制器, 模块被当成小应用主体来看待,和应用主体不同的是, 模块不能单独部署,必须属于某个应用主体。
Modules目录结构
——------------------------------------------------
forum/
Module.php 模块类文件
controllers/ 包含控制器类文件
DefaultController.php default 控制器类文件
models/ 包含模型类文件
views/ 包含控制器视图文件和布局文件
layouts/ 包含布局文件
default/ 包含 DefaultController 控制器视图文件
index.php index 视图文件
官方文档:https://www.yiichina.com/doc/guide/2.0/structure-modules
更详细
- 使用模块
要在应用中使用模块,只需要将模块加入到应用主体配置
的modules
属性的列表中, 如下代码的应用主体配置 使用 forum
模块:
[
'modules' => [
'forum' => [
'class' => 'app\modules\forum\Module',
// ... 模块其他配置 ...
],
],
]
- 路由
和访问应用的控制器类似,路由 也用在模块中控制器的寻址, 模块中控制器的路由必须以模块 ID 开始,接下来为控制器 ID 和操作 ID。 例如,假定应用使用一个名为
forum
模块, 路由forum/post/index
代表模块中post
控制器的index
操作, 如果路由只包含模块ID,默认为default
的 [yii\base\Module::defaultRoute-detail) 属性来决定使用哪个控制器/操作, 也就是说路由forum
可能代表forum
模块的default
控制器。
实际操作
1、新建模块目录Modules,在该目录下新建模块目录forum
forum就是一个模块了
2、新建如下
3、新建模块的控制器
PostController
4、新建Module.php
每个模块都有一个继承 yii\base\Module 的模块类, 该类文件直接放在模块的 base path 目录下, 并且能被 自动加载。当一个模块被访问, 和 应用主体实例 类似会创建该模块类唯一实例,模块实例用来帮模块内代码共享数据和组件。
params['foo'] = 'bar';
// ... 其他初始化代码 ...
//如果 init() 方法包含很多初始化模块属性代码, 可将他们保存在配置 并在 init() 中使用以下代码加载:
// 从config.php 加载配置来初始化模块
\Yii::configure($this, require __DIR__ . '/config.php');
}
}
5、config.php配置文件包含以下内容
[
// list of component configurations
],
'params' => [
// list of parameters
],
];
过滤器???中间件??
小部件
例如 一个jui部件的使用
先安装jui
composer require --prefer-dist yiisoft/yii2-jui
//./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Installing bower-asset/jquery-ui (1.12.1): Downloading (100%)
- Installing yiisoft/yii2-jui (2.0.7): Downloading (100%)
Package phpunit/php-token-stream is abandoned, you should avoid using it. No replacement was suggested.
Writing lock file
Generating autoload files
28 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
= DatePicker::widget(['name' => 'date']) ?>
效果:
使用小部件
1、在SiteController
中新增动作
public function actionForm()
{
$model = new Form();
//将表单 交给Form模型进行验证
if($model->load(Yii::$app->request->post()) && $model->validate())
{
//todo...
echo '';
var_dump(Yii::$app->request->post());die;
}
//显示表单
return $this->render('form',['model'=>$model]);
}
2、新增form
模型,并且定义三个属性
3、新建视图site/form.php
'form']); ?>
= $form->field($model, 'username') ?>
= $form->field($model, 'password')->passwordInput() ?>
= $form->field($model, 'date')->widget(DatePicker::class,[
'language' => 'cn',
'dateFormat' => 'php:Y/m/d',//按照这个格式传参
])?>
= Html::submitButton('Login') ?>
效果:
创建小部件
1、创建部件目录components
2、在components
目录下新建一个小部件类,继承\yii\base\Widget
render('hello',['content'=>$content]);
}
}
当小部件需要渲染很多内容,虽然你可以在 run()
方法中嵌入内容,但更好的方法是将内容放入一个视图文件, 然后调用 yii\base\Widget::render() 方法渲染该视图文件
小部件的视图一般都存放在components/views
下
3、直接在site/form.php
文件中使用我们的小部件HelloWidget
如下:
4、 在SiteController
中创建动作
public function actionForm()
{
$model = new Form();
if($model->load(Yii::$app->request->post()) && $model->validate())
{
//todo...
echo '';
var_dump(Yii::$app->request->post());die;
}
//$variable作为参数 将会交给 小部件作为内容输出
return $this->render('form',['model'=>$model,'variable'=>'这块内容是将要输出的内容 也就是在run方法中$content会取到的内容,如何将$content作为参数传递给小部件视图hello,我们可以将我们视图的布局,数据渲染都交给hello处理']);
}
5、创建小部件视图components/views/hello.php
';
?>
Document
效果: