最近准备要基于Yii框架写一个项目,于是准备自学一波,这里记录一下自己的笔记
首先我用的是phpstudy环境下搭建Yii框架,注意这里一定要用PHP5.4
以上的版本,我们可以通过Yii框架目录下的requirements.php
查看我们还差一些什么条件之类的。
搭建框架我这里用了两种方法去安装
第一种直接官网上下载源码,解压后放到phpstudy
的网站根目录下面,不过这种办法要自己手动设置cookie,这个文件在config
目录下的web.php
文件,这里最好设置到自己都不认得最好
也就是上图的红标的地方
第二种就是通过composer安装,这种方法就不用设置cookie,但这个方法下载的有点慢,而且安装过程中还会出现一系列毛病。。特别是关于那个SSL/TLS
的设置,最后我是在php-ini
里面把相关配置给弄好,而且在phpstudy里面把php-openssl
打开才解决的,
但是最后命令航还是有这样的一句话
但好像对整体没啥影响。。。
上面的配置弄好以后可以在浏览器中打开web目录下的index.php,如果出现以下界面表明安装成功。
为什么要提出命名空间概念,这是因为在文件包含的时候如果两个文件拥有相同的类的话,会出现下面的报错,这时候命名空间就会起到至关重要的作用了
解决办法就是在任意包含的一个文件里面改变命名空间,用using
语句,就像下图
我们在index页面可以这样去创建一个类,结果如下
但是出现多个一样的类,机会显得冗余
于是我们可以这样做,同时我们还可以利用as
去重命名类,这符合多个相同的类的文件包含的情况
在命名空间里面我们要注意的是如果不加using语句的话,默认是在一个全局命名空间里面,我们在用use语句是应该是这样的,千万要注意\
一般控制器的文件是放在controllers
目录下面的
一般命名的格式为name+Controller
,以Controller
这个单词结束
而在对应的文件里面的类我们也应该同名(因为我们所说的控制器大部分都是一个类,而且规定得放在一个命名空间里面)
而且定义的类必须的再继承控制器这一个类,所以extends
后面的东西不能少
但是extends
后面的Controller
也是一个类,也有自己的命名空间,于是我们还得加上use yii\web\Controller
;
里面的操作必须以action
开头加上名字
我们如何来引用这个操作,我们就要在入口脚本里面加上r=hello/index
,其中hello是指hello控制器,index是指hello控制器下的index的操作
控制器下传参的话一般我们可以调用一个全局的类YII,以这样的url为例子说明在代码里面是如何执行的,这个url表明了现在的操作是hello控制器下的index操作的传参操作
我们首先要调用全局类Yii下的静态变量$app,也就是应用主体里面的request方法
记得不要忘记$request = \Yii::$app->request
中的\缺了就可能报错,虽然官方文档没加,但是尝试过后发现还是要加上的,所以说前面为什么在命名空间里面提及要全局类前面加上\
对于get传参我们可以这样去利用
$request->get('id',20)
,第一个参数是get方式请求的名字,第二个是传参返回的默认值,还有post传参也是这样的操作
还有一个常用的方法就是判断他是post方法还是get方法
$request->isGet
,如下图
同样的Post也一样,举一反三而已
我们还能获取ip地址例如$request->userIp
更多的方法还可以去查官方手册
关于控制器的响应,我们可以利用这个东西去进行多个操作
首先这一个东西也需要调用全局类Yii下的静态变量$app,也就是应用主体里面的response
方法
假定前提条件是$res=\YII::$app->response
我们可以利用他来改变返回的状态码
$res->statusCode = '404'; //默认是200
我们还可以用来改变header的一些属性
$res->headers->add('pogram','1234556');
给头部增加属性
还能设置跟删除
$res->headers->set('pogram','123456');
$res->headers->remove('program');
还有跳转功能
$res->headers->add('location','http://www.baidu.com');
但是这一句话好像没什么反应。。官方手册我也没找到。。莫非被抛弃了?
但是由于这个功能比较常用,所以直接封装在Controller里面,这就有了第二种写法
$this->redirect('http://www.baidu.com',302); //这里的第二个参数是状态码
文件下载功能
$res->headers->add('content-disposition','attachment;filename="a.jpg"'); 这里的文件名自定义,但总觉得记着里面的参数有点困难
同样的因为常用而有一个更直接的send
方法
$res->sendFile("./robots.txt");
注意这里的文件是存在于web目录下也就是当前目录下的的文件
这一部分参考文档也很详细,我就选几个重要的来学习一下。。。
同样的这也是在全局类Yii下的应用主体app下的session方法
以$session = \YII::$app->session;
为例
判断是否设置了session用一个方法isActive方法
if($session->isAcive)
而打开session我们可以用open()函数
$session->open()
这里列举一下session的增删改查
$session->set('user','abcde'); //增设一条session数据,我们可以看到在PHPstudy里面的tmp目录下多了一条session数据
如果想获取这一条session里面的数据的话
我们可以这样去调用
$session->get('user');//在编译器里面echo一波他就会出现
$session->remove('user');
当然上面的办法是通过Yii框架里面的类去实现的,我们还可以利用数组方式去设置,例如
$session['user']='abcde';
echo $session['user'];
unset ($session['user']);
注意
如果我们在其他浏览器echo一下session的话,他是不会出现任何东西的,原因是他会识别session里面的文件名,进而把里面的内容拿出来,好像是不同的浏览器产生的session的文件名不一样。。
从响应
里面获取数据并往浏览器里面写入cookie数据,我们可以利用下面的语句
首先我们得从全局类Yii下的应用主体app下的response获取对应的cookies方法
也就是/YII::$app->response->cookies
;
我们添加一个cookies信息,可以这样去写
$cookies = \YII::$app->response->cookies;
$cookie_data = array('name'=>'user','value'=>'1232123');
$cookies->add(new Cookie($cookie_data)); //注意这里add参数里面是一个Cookie对象,引用这样的对象我们需要在代码
的前面使用一个命名空间use yii\web\Cookie;
如果我们要修改某一个cookie的值的话,直接改变value的值即可,他会自动覆盖掉原来的数据
删除的话我们就可以直接用$cookies->remove('id'); //里面是cookie的名字
从请求
里面拿出cookie数据,我们这个时候使用的是app下的request对应的cookies方法
$cookies = \YII::$app->request->cookies;
$cookies->getValue('user');
但如果上面的cookie不存在的话,他会返回第二个默认的值
$cookie->getValue('users',24);
cookie这东西为什么在浏览器里面看来是一串混乱的数字呢,是因为它用了我们上面总结的在config目录下的web.php文件里面的cookiekey加密的
过多的html或者js会给我们带来冗余的代码,这里就涉及到Yii框架给我们带来的一套解决方案,就是把一些前端代码放在一个叫views的文件夹下
我这里是在views文件夹下新建了一个hello文件夹对应上面的hello控制器,而且这个文件夹下存在一个名为index.php的文件,里面存有HTML格式
一般我们把里面的文件称为视图文件,而Yii框架规定了我们需要在对应控制器名字的文件夹下寻找对应的文件(文件夹不区分大写小好像)
我们在对应的控制器文件里面需要调用renderPartial函数,但是由于这个数属于Controller类,所以我们直接调用
return $this->renderPartial('views下对应的文件名/*为了方便管理Yii框架规定不要后缀名*/');
而且这里的return也是是Yii框架规定的,调用视图只能够这样写了
我们可以通过数组去传递视图要的数据,主要步骤是先创建一个数组,再利用数组里面的key和value
然后再利用renderPartial函数的第二个参数去传参
这里是controller页面的代码
$hello_str= 'Hello World';
$test_array = array(1,2);
//创建数组
$data = array();
//把需要传给视图的值放进数组中
$data['view_hellostr']=$hello_str;
$data['view_array']=$test_array;
return $this->renderPartial('index',$data);
在view下的hello/index.php下的代码
<h1>=$view_hellostr?>h1>
<h1>=$view_array[0]?>h1>
这里我们是利用了controller里面数组的键值作为这一个页面输出的变量,记得带$符号,我好几次都忘记了,还有的话数组记得加下标,不然会报错的
另外补充一个知识点
这里的利用了php的短标签
在php的配置文件(php.ini)中有一个short_open_tag
的值,开启以后可以使用PHP的短标签: ?>
同时,只有开启这个才可以使用 =
以代替 echo
,=表示输出,不过建议用
这里涉及到一个xss安全问题,学习视频发现这里应该是反射型xss,主要原因是过滤不严谨导致的。。复习一波安全知识
下面就是如何来防御的
我们把上面代码的变量$hello_str= 'Hello World';
改为$hello_str= 'Hello World';
这是一个弹窗代码,如果用户输入的是这样的,那就会直接弹窗,体验会很差
解决的办法有两个,一个是编码一下,再输出,另一种是直接过滤掉js代码
我们只需要在view页面下的hello/index.php下的代码做修改,但是我们需要提前使用它们的命名空间,所以前面加上
use yii\helpers\Html; //第一种方法的命名空间
use yii\helpers\HtmlPurifier; //第二种方法的命名空间
?>
=Html::encode($view_hellostr)?>
=HtmlPurifier::process($view_hellostr)?>
//利用了process的方法
页面显示结果如下
Hello World<script>alert('xss');script>
Hello World
有时候多个HTML文件会出现冗余的部分,程序员不希望出现这种情况
于是我们就出现了布局文件把冗余的部分放在一个文件下方便调用
一般我们会在views下面的layouts文件夹新建布局文件
这里我在layouts文件夹下创建了一个名叫common.php的文件
把公共部分的代码放在common文件下
回到controller的文件下,这时候我们不能再使用renderPartial
这个函数了,应该用render
这个函数,也就是该为
return $this->render('index'); //这里的代码的作用是先把index视图里面的内容放到一个叫$content的变量里面,而且会把布局文件里面的内容显示出来
但同时我们也要指定布局文件,所以我们要对应的类下面而不是控制器里面去定义public $layout='布局文件名字'
然后我们在common.php里面去添加=$content?>
下面是我comon.php里面的代码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
=$content?>
body>
html>
在views文件夹下的hello下面再建立两个文件,一个是index.php一个是about.php 我们要想在index.php里面显示about.php里面的内容的话,需要在index.php里面加上
echo $this->render('about'); ?>
如果我们需要往about页面里面传进参数的话,这时候我们需要的是这里的render函数的第二个参数
但这第二个参数必须是一个数组,我们可以这样
echo $this->render('about',array('view_hello'=>'Hello about 2')); ?>
如果我们还需要把传进的参数在about页面上显示的话
我们要在about页面下添加
=$view_hello?>
直接把数组的键值当成变量名在about页面输出即可
有时候我们需要覆盖掉common.php 里面的内容话,我们需要设置一个叫数据块的东西
我们在视图文件index.php里面定义数据块
$this->beginBlock('自定义的数据块的名字');?>
这两句话中间是你想要输入的东西
$this->endBlock();?>
然后在布局文件下显示出来的话
=$this->blocks['你定义的数据块的名字'];?>
如果在布局文件想要覆盖的话,需要一个if判断即可
if(isset($this->blocks['你定义的数据块的名字']))
建立了一个数据库后我们需要对数据库进行操作
这时候我们需要在models文件夹下建立一个属于自己的数据库操作类,新建一个跟数据库表名一样名字的一个类,定义一个类所需要的命名空间那些我就不多说了
这里我们主要继承了yii\db\ActiveRecord下的ActiveRecord类
test.php
里面的内容是这样的
namespace app\models;
use yii\db\ActiveRecord;
class Test extends ActiveRecord{
}
?>
查询的话我们依旧返回我们的HelloController里面去执行
首先我们得加上使用哪一个命名空间里面的类use app\models\test;
在actionIndex操作里面,如果查询的话可以这样来
$sql = 'select * from test where id=1 ';
$results = test::findBySql($sql)->all();
print_r($results);
这里调用了test所继承的活动记录里面的findBySql函数
去执行,这个时候这个函数返回的是一个对象,然后利用all
把所有的对象信息封装成一个数组
另外从安全角度去讲findBySql这个函数对于SQL注入有着很好的防范。。。又想起了SQL注入的各种骚操作emmmmm
利用这个函数的第二个参数加上占位符的方式去实现防范SQL注入
$sql = 'select * from test where id=:id ';
$results = test::findBySql($sql,array(':id'=>'1 or 1=1'))->all();
print_r($results);
这里的第二个参数就相当于对id这一个参数把1 or 1=1整型化变为1
,这样就能过滤大多数非法字符
但是每一次这样去写的话就有些麻烦
我们就可以换另外一种操作方式,直接用活动记录中的find函数下的各种where
下面就是几个例子
//id=1
$results = test::find()->where(['id'=>1])->all();
//id>0
$results = test::find()->where(['>','id',0])->all();
//id>=1 并且 id<=2
$results = test::find()->where(['between','id',1,2])->all();
//title like %title% 类似一种模糊匹配
$results = test::find()->where(['like','title','title1'])->all();
更多详细的查询方法可查看官方文档
上面我们讲了这个东西,是先把它转化为对象再把它转化为数组,但是我们知道有时候对象的内容比数组更大,容易消耗内存
所以其实我们可以把化成对象的这一步改变成变成数组就好了,所以就使用Yii框架里面的asArray()的函数,就是下面的样子
$results = test::find()->where(['between','id',1,2])->asArray()->all();
我们看一下效果图明显不一样了
在Yii框架中还有一种批量查询的方法,具体实现方法如下
foreach(test::find()->batch(2) as $tests)
{
print_r($tests);
}
这里面的batch
里面的数字表明一次从数据库中拿出几条数据,一直到取完为止
这两种都是为了优化内存而设计的办法,突然想起最近上的操作系统课的内存管理里面利用局部性原理去实现的交换技术emmmm
我们可以这样去操作
本来看视频操作这样是可以的
$results = test::find()->where(['id'=>1])->all();
$results[0]->delete();
但是在自己本机上就不怎么行,一直发出Notice的报错,没关系,我们还有另一种做法
直接调用类里面的方法
test::deleteAll();
函数里面的参数可以这样写,不写默认删除全部
删除id>1的可以这样去写
test::deleteAll('id>1');
同样这个函数还能用占位符表示来实现功能
test::deleteAll('id>:id',array(':id'=>1));
添加我们是先定义一个数据库的类然后利用save()函数去不得对象变为数据库里面一条数据,再去保存
下面是一个例子
$test1 = new test();
$test1->id=4;
$test->title=content4;
$test->save();
但是我们一般输入的值都是由用户输入的,所以我们还得进行一些检测,我们可以在活动记录中加入验证器
也就是一个rule,返回的一个数组下面是一个例子,更多例子我们可以在官方文档的核心验证器找到
public function reles()
{
return [
['id','integer'],
['title','string','length'=>[0,10]]
];
}
我解释一下这里的意思,就是第一个参数是字段名,第二个参数是字段类型,第三个参数是长度
然后下一步我们还是要回去控制器里面去执行多一步,加多一个hasError()错误判断去看一下是否为合法数据
$test->validate();
if($test->hasError())
{
echo "Something Wrong!";
die();
}
不过在我看来这还是有漏洞的,我尝试了一下如果是负数的id的话他也会插入,这就不符合常理,而且相同id的话他只会报错,不会执行相关代码
修改的话其实就是两种操作的融合
首先查询,然后去利用save去修改
这里是一个例子
$test = test::find()->where(['id'=>4])->one();
$test->title='content456';
$test->save();
到这里我们需要在models里面新建多几个模型,然后再控制器里面加上对应的命名空间
添加了customer还有order两个表,也因此添加了两个模式
先举例一个客户的订单信息,这是一个一对多的关系,这里我们提这一点是为了后面提到hasMany()还有hasOne()的区别
在控制器里面我们使用如下代码去关联两个数据表之间的数据
$customer1 = customer::find()->where(['name'=>'zhangsan'])->one();//这里返回的是一个对象,为的是下面使用的继承方法hasMany
$order1 = $customer1->hasMany('app\models\order',['customer_id'=>'id'])->all();
print_r($order1);
上面的语句要注意的是hasMany里面的两个ID只要注意一下位置,哪个前哪个后
这里其实我们还可改进一下app\models\order,这里有点长,我们可以直接用order::class
来表示,变成下面的语句
$order1 = $customer1->hasMany(order::class,['customer_id'=>'id'])->all();
直接把这样的代码放在控制器里面有时候会比较冗余
所以我们可以尝试这把这第二行代码封装进customer类里面
在models下的customer类里面我们这样定义一个函数,注意类里面的$this
class customer extends ActiveRecord{
public function getOrders(){
$order1 = $this->hasMany(order::class,['customer_id'=>'id'])->all();
return $order1;
}
}
然后我们在控制器里面就可以直接调用getOrders()这个函数
同样的还有一种方法就是
直接在控制器里面这样写
$orders = $customer1->orders;
这里是因为php的一个特性
如果在一个类里面不存在所写的变量就会在调用一个__get()
函数,这个函数会自动拼接上那个不存在的变量名所以就会变成getOrders()
,顺其自然地就会调用类里面已有的getOrder()
函数,这里的变量名好像大小写没有关系,我自己在本地试了下,没关系,返回的结果都是一样的
这里是从订单信息获取客户信息的例子
因为这里是一对一的关系,所以我们就用hasOne()的方法
public function getCustomer()
{
return $this->hasOne(customer::class,['id'=>'customer_id'])->asArray()->one();
}
这里我们用的是one()是为了对应一条数据。。