来自与《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',
        ],
        ....

请求处理流程

2017090722293application-structure.png

(图片来自:http://www.yiichina.com/doc/guide/2.0/structure-overview)

请求交给了应用主体 -> 应用主体处理请求之前会加载应用组件、以及相应的模块来更好的处理请求 -> 正式处理请求时会把请求的数据委托给控制器 -> 控制器通过模型来和数据库打交道 -> 需要展示的话会让视图去进行相应操作

控制器Controller创建

创建

新建一个HelloController.php,放到controller目录下。

  1. 需要使用命名空间namespace app\controllers;
  2. 需要继承自Controller,因此需要use yii\web\Controller;
  3. 不需要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/indexhttp://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');
    }

201709123287011.png

重定向

   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');中,如果找不到路径下的文件就会抛出异常:

2017091284652error.png

此处文件路径位置是相对于入口脚本(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的值存储的。

2017091433783cookie.png

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做两件事:

  1. 将about.php的内容放到$content文件中
  2. 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;
}
?>

浏览器输出结果:

2017091712648out.png

视图之数据块

指的是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>

20170918417351.png

需要注意的是:

  1. 如果布局文件没有指定<?=$this->blocks['block1'];?>,浏览器就不会显示block块里的内容;
  2. 同样的,如果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