来自与《Yii框架》不得不说的故事—基础篇
配置
入口脚本文件: config/web.php
cookieValidationKey
是为了防止cookie攻击的
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'components' => [
'request' => [
//用来防止cook攻击的
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '_FEdiSEYNINKhO2Pt8JCp33PkGvB4wBj',
],
....
请求处理流程
(图片来自:http://www.yiichina.com/doc/guide/2.0/structure-overview)
请求交给了应用主体 -> 应用主体处理请求之前会加载应用组件、以及相应的模块来更好的处理请求 -> 正式处理请求时会把请求的数据委托给控制器 -> 控制器通过模型
来和数据库打交道 -> 需要展示的话会让视图去进行相应操作
控制器Controller创建
创建
新建一个HelloController.php
,放到controller目录下。
- 需要使用命名空间
namespace app\controllers;
- 需要继承自
Controller
,因此需要use yii\web\Controller;
- 不需要
include
也可以使用外部对象,因为composer
帮我们做了这一操作,具体见composer.json
的配置
<?php
namespace app\controllers; //必须使用命名空间app\controller
use yii\web\Controller; //xxController继承于`controller`
class HelloController extends Controller
{
// http://localhost/Yii/basic/web/?r=hello/index
public function actionIndex(){
echo 'hello world';
}
}
访问http://localhost/Yii/basic/web/?r=hello/index
或http://localhost/Yii/basic/web/?r=hello
就可以看到'hello world'了。 (访问路径以自己实际配置的为准)
获取请求参数等
需要用到全局类Yii
,$app
为全局变量。可以使用``use Yii;或者
\Yii::$App`
public function actionIndex(){
//请求组件:
//Yii为全局类。 $app为全局变量 可以`use Yii;`或者 `\Yii:$app`
//http://localhost/Yii/basic/web/?r=hello/index&id=3
//http://localhost/Yii/basic/web/?r=hello/index&id=999
//如何处理参数
$request = Yii::$app -> request;
echo $request -> get('id');
//默认值为20,如果没有id参数的话
echo $request -> get('id',20);
//类似的
//post
//$request -> post('name',3333);
//判断是get还是其他
if ($request -> isGet){
echo '<br>是get请求';
}
//获取ip地址
echo $request -> userIP;
}
响应处理
返回状态码:
public function actionIndex2(){
//response组件
$res = Yii::$app -> response;
//设置返回码,如返回404
$res -> statusCode = 404;
}
可以设置响应头:
public function actionIndex2(){
//response组件
$res = Yii::$app -> response;
$res -> headers -> add('pragma','no-cache');
// $res -> headers -> set('pragma','max-age=5');
// $res -> headers -> remove('pragma');
}
重定向
public function actionIndex2(){
//response组件
$res = Yii::$app -> response;
//$res -> headers -> add('Location','http://www.baidu.com');
//yi controller 封装了redirect方法
//$this -> redirect('https://baidu.com');
$this -> redirect('https://baidu.com',302);
}
文件下载
public function actionIndex2(){
//response组件
$res = Yii::$app -> response;
//文件下载
//这样会下载一个 a.png的文件。 但是是空的,因为没有配置
//$res -> headers -> add('content-disposition','attachment;filename="a.png"');
//yi controller 封装了方法 ,但是如果没有文件会抛出异常
$res -> sendFile('./b.jpg');
}
$res -> sendFile('./b.jpg');
中,如果找不到路径下的文件就会抛出异常:
此处文件路径位置是相对于入口脚本(web/index,php
)的路径.
session
需要用到session
组件: $session = Yii::$app->session
.
判断是否开启: $session -> isActive
public function actionIndex3(){
$session = Yii::$app -> session;
//打开
$session -> open();
if ($session->isActive){
echo 'isActive';
}else{
echo 'not Active';
}
//这样可以存放数据了
//存放位置在php.ini中的session.save_path可以找到
$session -> set('user','张三');
echo $session->get('user');
//删除
$session -> remove('user');
echo $session->get('user').'</br>';
//其他操作,可以当成数组处理。原因是继承了ArrayAccess
$session['user'] = '小明';
echo $session->get('user').'</br>';
//删除
unset($session['user']);
echo $session->get('user').'</br>';
}
存储的sessionId可以在这里看到. 本地session目录下的文件也是根据sessionId的值存储的。
cookie
public function actionIndex4(){
$cookies = Yii::$app -> response ->cookies;
//从浏览器可以看到,cookie的值是加密的
$cookiesData = array('name' => 'user' , 'value' => 'zhangsan3');
$cookies -> add( new Cookie($cookiesData));
//删除某个cookie
//$cookies -> remove('user');
//取某个cookie值
echo $cookies -> getValue('user');
}
cookie如何加密,与onfig/web.php
中的cookieValidationKey
有关。
视图的创建
Yii允许把代码移动到view中来编写。
renderPartial
//HelloController.php文件
....
function actionIndex(){
//actionIndex和视图index.php对应
//视图的index.php需要放到`Views/Hello`目录下,目录名称与`Hello`Controller保持一致
//显示哪个view文件
return $this->renderPartial('index');
}
...
传递字典或数组参数
HelloController.php文件:
function actionIndex(){
$data = array();
$data['view_hello_str'] = 'Hello God!';
//数组元素的情况,如何读取
$data['view_test_arr'] = array(1,2);
//传参数
return $this -> renderPartial('index',$data);
//也可以用render,这样话会保留网站的header footer ,而不是一个新界面
//return $this ->render('index',$data);
}
对应的视图文件views/Hello/index.php
:
<?php
echo $view_hello_str; //浏览器会显示`Hello God`
echo '</br>';
echo $view_test_arr[0] ; //显示 `1`
视图的数组安全
举例:
function actionIndex(){
$data = array();
$data['view_hello_str'] = 'Hello God! <script> alert("hello world") </script>';
return $this -> renderPartial('index',$data);
}
这样会导致执行javascript代码,如果字符串是用户传过来的,可能会导致安全问题。
处理方法:
<?php
use yii\helpers\Html;
use yii\helpers\HtmlPurifier;
?>
<h1> <?=html::encode($view_hello_str)?> </h1> //会输出正常字符串:Hello God! <script> alert("hello world") </script>
<h1> <?=HtmlPurifier::process($view_hello_str)?> </h1> //输出`Hello God!`
布局文件
使用布局文件可以使我们少些很多重复代码。
需要用到render
方法:
render做两件事:
- 将about.php的内容放到
$content
文件中
- render方法把布局文件显示出来,布局文件通过
public $layout = 'common';
来指定
默认布局文件位置:views/layouts/main.php
创建一个布局文件: view/layouts/common.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
</head>
<body>
<?=$content;?>
</body>
</html>
controller:
...
public $layout = 'common';
public function actionAbout()
{
//render做两件事
//1.将about.php的内容放到`$content`文件中
//2.render方法把布局文件`common`显示出来,布局文件通过`public $layout = 'common';`来指定
return $this->render('about');
}
...
视图文件view2/Hello/index.php
:
<h2> about page</h2>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
视图中显示其他视图(视图组件)
以视图:index.php
为例:
<!--引入其他视图-->
<?php echo $this -> render('about'); ?>
<!--传参数-->
<?php echo $this -> render('about',array('v_hello_str' => 'hello world')); ?>
view: about.php
<h2> this is about page</h2>
<?php
if (isset($v_hello_str)){
echo $v_hello_str;
}
?>
浏览器输出结果:
视图之数据块
指的是layout
使用view
文件的代码块。
举例:
controller指定了布局文件为common.php
...
public $layout = 'common';
public function actionAbout()
{
return $this->render('about');
}
...
view/Hello/about.php
文件:
<h1> this is about page</h1>
<?php $this -> beginBlock('block1');?>
<h2> 这是被替换掉的部分</h2>
<?php $this -> endBlock(); ?>
布局文件common.php
,使用<?=$this->blocks['block1'];?>
可以将视图文件的block部分替换掉。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<?=$this->blocks['block1'];?>
<?=$content;?>
</body>
</html>
需要注意的是:
- 如果布局文件没有指定
<?=$this->blocks['block1'];?>
,浏览器就不会显示block块里的内容;
- 同样的,如果view文件没有指定
beginBlock..
,浏览器就不会显示block块里的内容。
布局文件可以通过isset
判断是否view文件是否包含相应的代码块:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<?php if (isset($this->blocks['block1'])):?>
<?=$this->blocks['block1'];?>
<?php else:?>
<h1>没有block1代码块</h1>
<?php endif;?>
<?=$content;?>
</body>
</html>
数据模型
数据库配置
创建数据库并插入一条数据:
mysql> create database yii;
Query OK, 1 row affected (0.01 sec)
mysql> use yii;
Database changed
mysql> create table test2 (
-> id int auto_increment primary key ,
-> title varchar(20)
-> ) CHARSET=utf8;
mysql> insert into test2(title) values('小明');
Query OK, 1 row affected (0.00 sec)
数据库配置文件为config/db.php
:
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=yii',
'username' => 'root',
'password' => '123',
'charset' => 'utf8',
];
数据模型之活动记录的创建
新建models/test2.php
文件,与数据库表名相对应。
<?php
namespace app\models;
use yii\db\ActiveRecord;
//继承于`ActiveRecord`
class Test2 extends ActiveRecord
{
//如果要修改表名,重写这个方法
public static function tableName()
{
return parent::tableName(); // TODO: Change the autogenerated stub
}
}
操作数据库表:
...
use app\models\Test2;
...
class HelloController extends Controller
{
public function actionIndex(){
$sql = 'select * from test2 where id = 1 && 1=1';
$result = Test2::findBySql($sql) -> all();
print_r($result);
}
}
防止sql注入
如果用字符串拼接sql,可能会有问题,例如:
$sql = 'select * from test2 where id = 1 || 1 = 1';
提供占位符的形式:
$sql = 'select * from test2 where id = :id';
$result = Test2::findBySql($sql,array(':id' => 1 )) -> all();
print_r($result);
运行时会将占位符看成一个整体。
此外提供了更优雅的方式:
$result = Test2::find()->where(['id' => 2]) -> all();
print_r($result);
>
$result = Test2::find()->where(['>','id', '1']) -> all();
print_r($result);
between
$result = Test2::find()->where(['between','id', 1,2]) -> all();
print_r($result);
like
$result = Test2::find()->where(['like','title',"小明"]) -> all();
print_r($result);
查询优化
将查询结果(对象)转换为数组:
$result = Test2::find()->where(['like','title',"小明"])-> asArray() -> all();
print_r($result);
//查询结果
Array
(
[0] => Array
(
[id] => 1
[title] => 小明
)
)
批量查询: 分批
//配合foreach
foreach (Test2::find() -> batch(1) as $tests){
print_r($tests);
}
删除
删除一条:
$result = Test2::find() -> where(['id' => 1]) -> all();
$result[0] -> delete();
删除多条:
Test2::deleteAll('id>:id',array(':id'=>0));
添加
使用save()
方法:
//添加数据
$test = new Test2();
$test -> title = "小黑";
$test -> save();
model中的验证举例:
public function rules()
{
return [
['title','string','length'=> [0,5]]
];
}
进行验证:
//添加数据
$test = new Test2();
$test -> title = "小黑小黑小黑";
$test -> validate();
if ($test -> hasErrors()){
echo 'data is error';
die;
}
$test -> save();
数据修改
修改,然后使用save保存
$test = Test2::find()->where(['id' => 4]) -> one();
$test->title = 't4';
$test->save();
关联查询
测试数据:
mysql> create table customer (
-> id int auto_increment primary key ,
-> name varchar(20)
-> ) CHARSET=utf8;
mysql> create table myorder( id int auto_increment primary key , customer_id int , price int);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from customer;
+----+--------+
| id | name |
+----+--------+
| 1 | 小明 |
| 2 | 小白 |
+----+--------+
2 rows in set (0.00 sec)
mysql> insert into myorder(customer_id,price) values('1', 20);
Query OK, 1 row affected (0.01 sec)
mysql> insert into myorder(customer_id,price) values('2', 120);
Query OK, 1 row affected (0.00 sec)
mysql> select * from myorder;
+----+-------------+-------+
| id | customer_id | price |
+----+-------------+-------+
| 1 | 1 | 20 |
| 2 | 2 | 120 |
+----+-------------+-------+
2 rows in set (0.00 sec)
order模型:
namespace app\models;
use yii\db\ActiveRecord;
class Order extends ActiveRecord
{
public static function tableName()
{
return "myorder";
}
}
关联查询
//关联查询 根据顾客查询订单信息
$customer = Customer::find()->where(['name' => '小明']) -> one();
//关联查询的方法
$order = $customer->hasMany('app\models\Order',['customer_id' => 'id']) -> all();
print_r($order);
也可以使用静态方法获取类名:
$order = $customer->hasMany(Order::className(),['customer_id' => 'id']) -> all();
将查询的代码封装到Customer
类中:
<?php
namespace app\models;
use yii\db\ActiveRecord;
class Customer extends ActiveRecord
{
//顾客获取订单信息
public function getOrders(){
//关联查询 根据顾客查询订单信息
//$customer = Customer::find()->where(['name' => '小明']) -> one();
//关联查询的方法
//$order = $customer->hasMany('app\models\Order',['customer_id' => 'id']) -> all();
$order = $this->hasMany(Order::className(),['customer_id' => 'id']) -> asArray() ; //->all();
//print_r($customer->getOrders());
//如果要使用
//print_r($customer->orders);
//访问不存在的属性时,会访问`__get()`方法,自动调用get方法,自动补上->all()方法
//会冲突,因此去掉 -> all(),使用 ->orders返回同样结果
return $order;
}
}
根据订单查询用户(hasOne查询单个):
namespace app\models;
use yii\db\ActiveRecord;
class Order extends ActiveRecord
{
public function getCustomer(){
//一个订单对应一个顾客
return $this -> hasOne(Customer::className(),['id' => 'customer_id']);
}
public static function tableName()
{
return "myorder";
}
}
/**使用
$order = Order::find() -> where(['id' => 1]) -> one();
$res = $order -> customer;
print_r($res);
*/
关联查询注意点
1). 关联查询的结果缓存可以通过unset清除
$order = Order::find() -> where(['id' => 1]) -> one();
$res1 = $order -> customer;
unset($order -> customer);
$res2 = $order -> customer; //重新执行了 select * from ....
2). 关联查询的多次查询
//查询全部用户
$customer::find() -> all();
foreach ($customers as $customer){
//查询每个用户的订单信息
$orders = $customer -> orders;
}
如果有100个用户会执行sql (1+100) 次。
优化:
$customers = Customer::find()->with('orders') -> all();
给用户的orders属性填上值
执行了select * from a where id in (select id from b );
:
select * from customer ;
select * from order where customer_id in(...)
参考: http://www.cnblogs.com/beijingstruggle/p/5885137.html