Yii2.0 记录下首次接触这个框架学习的点点滴滴
目录 关于 .env
init()
代替 __construct
控制器 路由 模型 视图 响应 异常 日志 缓存 关于 .env
Yii默认是使用 main-local.php
来代表本地环境,会覆盖掉main.php
的配置。
参考 Tinkphp5
源码,自定义函数 env()
让框架支持读取 .env
文件配置。
使用方法:
在项目入口文件前引用 env.php
文件即可。
env.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php defined('ENV_PREFIX' ) or define('ENV_PREFIX' , 'PHP_' ); $envFilePath = __DIR__ . '/../.env' ; if (is_file($envFilePath)) { $env = parse_ini_file(__DIR__ . '/../.env' , true ); foreach ($env as $key => $val) { $name = ENV_PREFIX . strtoupper($key); if (is_array($val)) { foreach ($val as $k => $v) { $item = $name . '_' . strtoupper($k); putenv("$item=$v" ); } } else { putenv("$name=$val" ); } } } if (!function_exists('env' )) { function env ($name, $default = null) { $result = getenv(ENV_PREFIX . strtoupper(str_replace('.' , '_' , $name))); if (false !== $result) { if ('false' === $result) { $result = false ; } elseif ('true' === $result) { $result = true ; } return $result; } return $default; } }
init()
代替 __construct
Yii
里大部分类的构造方法都需要传参并调用父类构造方法。
为了方便实现构造方法,Yii
每个父类都会定义一个 init()
空方法,并在构造方法使用。
所以当你需要使用构造方法的时候请选择 init()
。
控制器 控制器中允许路由访问的方法都需要加上 action
+ 方法名才可以访问。例: actionIndex()
。
actions() 比如访问 http://host/site/test
的时候,会先在控制器的 action()
方法中找到对应请求的 test
方法
如果没有那么就会在控制器中找 actionTest()
方法
把公共的方法放在 actions()
中,这样要对调用一些公共的静态页面时就可以不用写控制器方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function actions () { return [ 'error' => [ 'class' => 'yii\web\ErrorAction' , ], 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction' , 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null , ], 'tests' =>[ 'class' =>'backend\models\TestAction' , ] ]; }
behaviors() 在控制器方法执行之前,使用指定的 过滤器
处理数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public function behaviors () { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['logout' ], 'rules' => [ [ 'actions' => ['logout' ], 'allow' => true , 'roles' => ['@' ], ], ], ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'logout' => ['post' ], ], ], ]; }
验证器 定义验证器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php namespace app \forms \common ;use app \forms \BaseForm ;class PhoneVerifyCodeForm extends BaseForm { public $phone; public $captcha; public $method = 'get' ; public function attributeLabels () { return [ 'phone' => '手机号' , ]; } public function rules () { return [ [['phone' ], 'required' ] ]; } }
验证数据
1 2 3 4 $form = new PhoneVerifyCodeForm(); $form->load(['PhoneVerifyCodeForm' => \Yii::$app->request->get();]);
路由 定义路由 对应模块的 config
目录下的 main.php
1 2 3 4 5 6 7 8 9 10 ...... 'urlManager' => [ 'enablePrettyUrl' => true , 'showScriptName' => false , 'rules' => [ 'login' => 'site/show-login-form' , 'news/detail/<id:\d+>' => 'news/detail' , ] ],
生成路由 1 2 \yii\helpers\Url::toRoute(["news/detail" , 'id' => $v['id' ]])
模型 查询器 1 2 3 4 5 6 7 8 9 10 11 12 13 $query = Model::find(); $query->where(['id' => 1 ]); $query->where(['in' , 'id' , [1 , 2 , 3 ]); \Yii::$app->db->createCommand($sql)->queryAll(); $query->asArray() $query->orderBy('sort desc, push_time desc' ); $query->all();
查看生成的sql语句 1 $query->where(['id' => 1 ])->createCommand()->getRawSql();
视图 渲染 布局 当控制器调用 render()
渲染视图时,默认会使用 @app/views/layouts/main.php
作为布局文件。
例如:下面代码使用 post.php
作为布局文件。
1 2 3 4 5 6 7 8 9 10 namespace app \controllers ;use yii \web \Controller ;class PostController extends Controller { public $layout = 'post' ; }
小部件 可以重复利用的视图都可以将其用小部件来展示
小部件类:model/widget/DemoWidget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php namespace app \widgets ;use app \services \DemoService ;use yii \base \Widget ;class DemoWidget extends Widget { public $data; public function init () { $service = new DemoService(); $this ->data = $service->getData(10 ); } public function run () { return $this ->render('demo' , [ 'data' => $this ->data ]); } }
小部件视图文件: model/widget/views/demo.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <div class="xw_wp sw"> <div class="con_r"> <span class="ln"></span> <span class="ti">新闻排行</span> </div> <ul class="xw"> <?php $newsNum = 1; if (is_array($data)) { foreach ($data as $v) { ?> <li> <a href="<?= \yii\helpers\Url::toRoute(["news/detail", 'id' => $v['id']])?>"> <span class="ic dls"><?=$newsNum ?></span> <span class="ti dls"><?=\yii\helpers\Html::encode($v['title']) ?></span> </a> </li> <?php $newsNum++; }} ?> </ul> </div>
分页 控制器方法 app/controller
1 2 3 4 5 6 7 8 9 10 11 12 13 ...... public function actionList () { $count = $this ->newsService->getNewsCount(); $pagination = new Pagination([ 'totalCount' => $count, 'defaultPageSize' => 2 , ]); $list = $this ->newsService->getNewsList($pagination->limit, $pagination->offset); return $this ->render('list' , compact('list' , 'pagination' )); }
视图 app/views
1 2 3 4 5 6 7 8 9 10 11 12 <?php ...... // 显示分页 echo \yii\widgets\LinkPager::widget([ 'pagination' => $pagination, 'nextPageLabel' => '下一页', // 设置下一页的显示文本 'prevPageLabel' => '上一页', // 设置上一页的显示文本 'options' => [ // 该项的设置的属性都会添加到分页组件的 <ul> 标签上 'class' => 'my-class-name', ] ]); ?>
响应 响应对象包含的信息有HTTP状态码,HTTP头和主体内容等。
从本质上说,网页应用开发最终的目标就是根据不同的请求去构建这些响应对象 。
状态码 手动设置状态码:
1 Yii::$app->response->statusCode = 200 ;
如果需要指定请求失败,可抛出对应的 HTTP
异常
响应头 1 2 3 4 5 6 7 8 9 10 $headers = Yii::$app->response->headers; $headers->add('Pragma' , 'no-cache' ); $headers->set('Pragma' , 'no-cache' ); $values = $headers->remove('Pragma' );
请求头大小写敏感。
响应主体 返回数据前先设置格式,format
属性指定 data
中数据格式化后的样式,例如:
1 2 3 $response = Yii::$app->response; $response->format = \yii\web\Response::FORMAT_JSON; $response->data = ['message' => 'hello world' ];
Yii
支持五种格式:
跳转 控制器中直接使用 redirect()
方法进行重定向
1 2 3 4 public function actionDemo () { return $this ->redirect('http://example.com/new' , 301 ); }
redirect()
方法默认为302,该状态码会告诉浏览器请求的资源临时放到了另一个URL地址上。
可传递 301 状态码告诉浏览器请求的资源已经永久重定向到新的URL地址。
如果请求为 Ajax
请求的时候,发一个 Localtion
头不会使浏览器自动跳转。
可设置一个 X-Redirect
头,让客户端用 js
获取并实现跳转。
非控制器中使用如下代码完成跳转
1 \Yii::$app->response->redirect('/' , 301 )->send();
错误处理 Yii
内置了一个 ErrorHandler
用来处理错误。
ErrorHandler 默认启用,可以在应用入口脚本定义 YII_ENABLE_ERROR_HANDLER
来禁用
自定义错误处理动作 应用配置文件 main.php
1 2 3 4 5 6 7 return [ 'components' => [ 'errorHandler' => [ 'errorAction' => 'site/error' , ], ] ];
如果异常不是继承于 UserException
,且 debug
为 true
时。
例: ErrorException
是不会走配置的方法而是直接使用默认的视图显示错误。
所以上线时必须关闭 debug
才能让所有异常错误走自定义的错误动作。
方便记录日志。
错误处理器 yii\base\ErrorHandler
中注释掉105行的 $this->logException($exception)
错误处理器默认会把每次异常记录为 error
级别的日志。
获取异常相关信息 错误处理动作中获取异常相关信息:
1 2 3 4 $this ->exception = \Yii::$app->errorHandler->exception; $code = $this ->exception->statusCode $message = $this ->exception->getMessage(); $trace = $this ->exception->getTrace();
可通过判断异常类是否继承于 UserException
决定是否记录日志
1 2 3 if (!$exception instanceof UserException) { }
如果异常继承于 UserException
会被认为是用户产生的错误,开发人员不需要去修正。
如果是 UserException
只需要返回友好的提示信息给用户即可。
日志 Yii
提供了一个日志框架,记录各种类型 的消息,过滤它们,并将它们收集到不同目标,例:文件,数据库。
log
组件必须在 bootstrapping
期间就被加载,以便它能够及时调度日志信息到目标里。
日志消息 这些方法可填两个参数
message
代表要被记录的日志信息
category
代表要被记录的日志类别
日志消息可以是字符串,也可以是复杂的数据,诸如数组或者对象。
可用魔术常量 METHOD 等当作日志类别区分日志
日志目标 一个日志目标是一个 yii\log\Target
类或者它的子类的实例。
它通过严重级别和类别过滤日志信息,然后将它们导入一些媒介中。
config\main.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 return [ 'bootstrap' => ['log' ], 'components' => [ 'log' => [ 'targets' => [ [ 'class' => 'yii\log\FileTarget' , 'categories' => ['app' ,], 'logFile' => '@runtime/logs/app.log' , 'logVars' => ['_GET' ,'_POST' ,'_COOKIE' ] ], [ 'class' => 'yii\log\FileTarget' , 'levels' => ['error' , 'warning' ], 'logFile' => '@runtime/logs/bug.log' , ], ], ], ], ];
自定义日志格式 重写 yii\log\FileTarget
中的 getContextMessage()
和 getMessagePrefix()
。
app\exceptions\FileLogHandler.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <?php namespace app \exceptions ;use yii \log \FileTarget ;use Yii ;class FileLogHandler extends FileTarget { protected function getContextMessage () { $json = []; $server = []; $result = '' ; foreach ($this ->logVars as $name) { if (!empty ($GLOBALS[$name])) { $key = strtolower(substr($name, 1 )); if ($key == 'server' ) { $server['SERVER_PROTOCOL' ] = empty ($GLOBALS[$name]['SERVER_PROTOCOL' ]) ? '' : $GLOBALS[$name]['SERVER_PROTOCOL' ]; $server['REDIRECT_STATUS' ] = empty ($GLOBALS[$name]['REDIRECT_STATUS' ]) ? '' : $GLOBALS[$name]['REDIRECT_STATUS' ]; $server['REQUEST_METHOD' ] = empty ($GLOBALS[$name]['REQUEST_METHOD' ]) ? '' : $GLOBALS[$name]['REQUEST_METHOD' ]; $server['REQUEST_URI' ] = empty ($GLOBALS[$name]['REQUEST_URI' ]) ? '' : $GLOBALS[$name]['REQUEST_URI' ]; $server['HTTP_ACCEPT' ] = empty ($GLOBALS[$name]['HTTP_ACCEPT' ]) ? '' : $GLOBALS[$name]['HTTP_ACCEPT' ]; $server['HTTP_USER_AGENT' ] = empty ($GLOBALS[$name]['HTTP_USER_AGENT' ]) ? '' : $GLOBALS[$name]['HTTP_USER_AGENT' ]; } else { $json[$key] = $GLOBALS[$name]; } } } if (!empty ($json)) { $result .= json_encode($json, JSON_UNESCAPED_UNICODE); } if (!empty ($server)) { if ($result) { $result .= PHP_EOL; } $result .= json_encode($server, JSON_UNESCAPED_UNICODE); } return $result; } public function getMessagePrefix ($message) { if ($this ->prefix !== null ) { return call_user_func($this ->prefix, $message); } if (Yii::$app === null ) { return '' ; } $request = Yii::$app->getRequest(); $ip = $request instanceof Request ? $request->getUserIP() : '-' ; $user = Yii::$app->has('user' , true ) ? Yii::$app->get('user' ) : null ; if ($user && ($identity = $user->getIdentity(false ))) { $userID = $identity->getId(); } else { $userID = '-' ; } return "[$ip][$userID]" ; } }
对应的 config\main.php
中的 class
需要设置为自定义的日志类
1 2 3 4 5 6 7 8 9 10 11 12 ..... 'components' => [ 'log' => [ 'targets' => [ [ 'class' => 'app\exceptions\FileLogHandler' , 'levels' => ['error' , 'warning' ], 'logFile' => '@runtime/logs/bug.log' , ], ], ], ]
注释抛出异常会自动记录日志 yii\base\ErrorHandler
中的 handleException()
注释掉 $this->logException($exception)
否则每次抛出异常的时候 Yii
都会自动记录一个 error 级别的日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ........ public function handleException ($exception) { if ($exception instanceof ExitException) { return ; } $this ->exception = $exception; $this ->unregister(); if (PHP_SAPI !== 'cli' ) { http_response_code(500 ); } try { ......
缓存 Redis composer
安装
1 composer require --prefer-dist yiisoft/yii2-redis
添加配置文件 main.php
1 2 3 4 5 6 7 ....... 'redis' => [ 'class' => 'yii\redis\Connection' , 'hostname' => 'localhost' , 'port' => 6379 , 'database' => 0 , ],
调用
1 2 3 4 5 $redis = Yii::$app->redis; $redis->set('test' , 123 ); $redis->expire('test' , 60 ); $redis->get('test' ); $redis->del('test' );
用户认证 配置 main.php
配置文件下在 components
下添加 user
组件
1 2 3 4 5 6 7 8 ...... 'components' => [ 'user' => [ 'identityClass' => 'common\models\User' , 'enableAutoLogin' => true , 'identityCookie' => ['name' => '_identity-frontend' , 'httpOnly' => true ], ] ]
cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击。
模型 common\models\User
对应的用户表模型
使用 cookie
登录的话表中必须有字段 auth_key
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <?php namespace common \models ;use yii \db \ActiveRecord ;use yii \web \IdentityInterface ;class User extends ActiveRecord implements IdentityInterface { public static function tableName () { return '{{%users}}' ; } public static function findIdentity ($id) { return static ::findOne($id); } public static function findIdentityByAccessToken ($token, $type = null) { return static ::findOne(['access_token' => $token]); } public function getId () { return $this ->id; } public function getAuthKey () { return $this ->auth_key; } public function validateAuthKey ($authKey) { return $this ->getAuthKey() === $authKey; } public function beforeSave ($insert) { if (parent ::beforeSave($insert)) { if ($this ->isNewRecord) { $this ->auth_key = \Yii::$app->security->generateRandomString(); } return true ; } return false ; } }
调用 1 2 3 4 \Yii::$app->user->login($user, 3600 * 24 ); \Yii::$app->user->getId();