入门
前置知识
php yii是一个php框架,要确保熟悉类与对象及命名空间
安装
通过归档文件安装
1.从 yiiframework.com 下载归档文件
2.将下载的文件解压缩到 Web 访问的文件夹中
3.将 basic/web 设置为文档根目录(document root)
运行
应用结构
每个应用都有一个入口脚本web/index.php,这是整个应用中唯一可以访问的php脚本,入口脚本接受一个web请求并创建应用实例去处理它
应用在它的组件辅助下解析请求,并分派请求至mvc元素
视图使用小部件去创建复杂和动态的用户界面
请求生命周期
1.用户向入口脚本web/index.php发起请求
2.入口脚本加载应用配置并创建一个应用实例去处理请求
3.应用通过请求组件解析请求的路由
4.应用创建一个控制器实例去处理请求
5.控制器创建一个动作实例并针对操作执行过滤器
6.如果任何一个过滤器返回失败,则动作取消
7.如果所有过滤器都通过,动作将被执行
8.动作会加载一个数据模型,或许是来自数据库
9.动作会渲染一个视图,把数据模型提供给它
10.渲染结果返回给响应组件
11.响应组件发送渲染结果给用户浏览器
说声hello
如何创建一个动作去响应请求
如何创建一个视图去构造响应内容
以及一个应用如何分派请求给动作
创建动作
操作必须声明在控制器中,为了简单起见,直接在SiteController控制器里声明say操作。这个控制器是由文件controllers/SiteController.php定义的
<?php
namespace appcontrollerss;
use yiiwebController;
class SiteController extends Controller
{
// ...现存的代码...
// 为了hello,需要创建一个say操作,
// 从请求中接收message参数并显示给最终用户
// 如果请求没有message参数,操作将显示默认参数Hello
// say操作被定义为actionSay方法
// Yii使用action前缀区别普通方法和操作
// action前缀后面的名称被映射为操作的id
public function actionSay($message = 'Hello')
{
return $this->render('say', ['message' => $message]);
}
}
给操作命名时,要先理解yii处理操作id。操作id总是被以小写处理。如果一个操作id由多个单词组成,单词之间将由连字符连接(如create-comment)。操作id映射为方法名时移除了连字符,将第一个单词首字母大写,并加上action前缀。如操作id create-comment相当于方法名actionCreateComment
在操作方法中,render()被用来渲染一个名为say的视图文件。message参数也被传入视图,这样就可以在里面使用。操作方法会返回渲染结果。结果会被应用接收并显示给最终用户的浏览器
当操作中调用了render()方法时,它将会按views/控制器ID/视图名.php路径加载php文件
创建视图
views/site/say.php
message参数被输出之前被html-encoded方法处理过
因为参数中可能隐含的恶意js代码会导致跨站脚本xss攻击
<?php
use yiihelpersHtml;
?>
<?= Html::encode($message) ?>
say视图脚本输出的内容将会响应结果返回给应用。应用将依次输出结果给最终用户
试运行
http://hostname/index.php?r=site/say&message=Hello+World
会输出hello world的页面
新页面和其它页面使用同样的头部和尾部是因为render()方法会自动把say视图执行的结果嵌入称为布局的文件中,本例中是views/layouts/main.php
上面url参数r代表路由,指向特定操作的独立id。路由格式是控制器id/操作id。应用接受请求的时候会检查参数,使用控制器id去确定哪个控制器被用来处理请求。然后相应控制器将使用操作id去确定哪个操作方法将被用来做具体工作。上述例子中,路由site/say将被解析至SiteController控制器和基本的say操作
使用表单
创建一个让用户提交数据的表单页
为了实现这个目标,需要创建一个操作和两个视图外,还需要创建一个模型
创建模型
models/EntryForm.php
namespace appmodels;
use Yii;
use yiiaseModel;
class EntryForm extends Model
{
public $name;
public $email;
public function rules()
{
return [
[['name', 'email'], 'required'],
['email', 'email'],
];
}
}
上面声明的验证规则表示
name和email值必填
email的值必须满足email规则验证
$model = new EntryForm();
$model->name = 'qiang';
$model->email = 'bad';
//使用validate方法触发数据验证
if ($model->validate()) {
//成功
} else {
//失败
//如果有数据验证失败,将把hasErrors属性设true,想知道具体发生什么错误就调用getErrors
}
创建动作
controllersSiteController
class SiteController extends Controller
{
public function actionEntry()
{
//用户提交表单后,
//操作将会渲染一个名为entry-confirm的视图去确认用户输入的数据,
//如果没填表单就提交,或数据验证不通过,entry视图将会渲染输出
$model = new EntryForm;
if ($model->load(Yii::$app->request->post())&& $model->validate()) {
//验证$model收到的数据
return $this->render('entry-confirm', ['model' => $model]);
} else {
//无论是初始化显示还是数据验证错误
return $this->render('entry', ['model' => $model]);
}
}
}
创建视图
site/entry-confirm.php
entry-confirm视图简单地显示提交的name和email数据
<?php
use yiihelpersHtml;
?>
<p>You have entered the following information:</p>
<ul>
<li><label>Name</label>: <?= Html::encode($model->name) ?></li>
<li><label>Email</label>: <?= Html::encode($model->email) ?></li>
</ul>
entry视图显示一个HTML表单
site/entry.php
使用了小部件ActiveForm生成html表单
先用begin()和end()分别用来渲染表单的开始和关闭标签
在这两个方法之间使用了field()方法创建输入框
最后使用yiihelpersHtml::submitButton()方法生成提交按钮
<?php
use yiihelpersHtml;
use yiiwidgetsActiveForm;
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'email') ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
用浏览器访问看是否工作
效果说明
没输入正确的信息时不需要刷新页面就能给出错误提示
其实数据首先由客户端js脚本验证,然后才会提交给服务器通过php验证
警告:客户端验证是提高用户体验手段,无论是否启用,服务端验证都是必须的
输入框的文字标签是field()方法生成的,内容就是模型中该数据的属性名,例如模型中name属性生成的标签就是Name,当然这些都是可以自定义的
<?= $form->field($model, 'name') -> label('自定义 Name')?>
<?= $form->field($model, 'email') -> label('自定义 email')?>
使用数据库
创建一个从数据表country中读取数据并显示
准备数据库
创建一个名为yii2basic的数据库
然后在数据库中创建一个名为 country 的表并插入简单的数据。可以执行下面的语句:
CREATE TABLE `country` (
`code` CHAR(2) NOT NULL PRIMARY KEY,
`name` CHAR(52) NOT NULL,
`population` INT(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `country` VALUES ('AU','Australia',18886000);
INSERT INTO `country` VALUES ('BR','Brazil',170115000);
INSERT INTO `country` VALUES ('CA','Canada',1147000);
INSERT INTO `country` VALUES ('CN','China',1277558000);
INSERT INTO `country` VALUES ('DE','Germany',82164700);
INSERT INTO `country` VALUES ('FR','France',59225700);
INSERT INTO `country` VALUES ('GB','United Kingdom',59623400);
INSERT INTO `country` VALUES ('IN','India',1013662000);
INSERT INTO `country` VALUES ('RU','Russia',146934000);
INSERT INTO `country` VALUES ('US','United States',278357000);
配置数据库连接
config/db.php
<?php
return [
'class' => 'yiidbConnection',
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
上面的数据库连接可以在应用中通过Yii::$app->db表达式访问
创建控制器
新建一个控制器
controllersHomeController.php
namespace appcontrollers;
use yiiaseController;
class HomeController extends Controller
{
public function actionIndex() {
//
}
}
测试工具
工具函数
helper/function.php
function p($var) {
echo '<pre>';
print_r($var);
echo '</pre>';
}
function dd($var) {
echo '<pre>';
var_dump($var);
echo '</pre>';
//加了die阻止后续的操作,不会渲染公共的页头和页尾
die;
}
引入
webindex.php
require(__DIR__.'/../helper/function.php');
使用
controllersHomeController.php
namespace appcontroller;
use yiiaseController;
class HomeController extends Controller
{
public function actionIndex()
{
$data = [
'name' => 'houdunwang',
'age' => 7
];
dd($data);
}
}
查看输出
使用print_r
使用var_dump
接收参数
controllersHomeController.php
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
// 接收get参数,默认值1
echo $request->get('id', 1);
// 也可以通过$request->isPost判断是什么方式请求的
}
public function actionHome()
{
$request = Yii::$app->request;
// 接收post参数
echo $request->post('username');
}
}
分配视图
controllersHomeController.php
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
$data = $request->userIP;
//render 输出父模板的内容,将渲染的内容,嵌入父模板。
//renderPartial 则不输出父模板的内容。只对本次渲染的局部内容进行输出
//视图名和方法名一般保持一致
return $this->renderPartial('index', ['data' => $data]);
}
}
viewshomeindex.php
<h1>hello</h1>
<?php echo $data;?>
输出
多个参数时建议使用如下的方式写
controllersHomeController.php
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
$data = [
'username' => 'houdun',
'ip' => $request->userIP,
'arr' => [
'age' => 7,
'class' => 5,
]
];
//注意这里renderPartial的第二个参数
return $this->renderPartial('index', $data);
}
}
viewshomeindex.php
<h1>hello</h1>
<h2><?php echo $username;?></h2>
<h2><?php echo $ip;?></h2>
<h2><?php echo $arr['age'];?></h2>
<h2><?php echo $arr['class'];?></h2>
输出
用下面的方法写可以得到相同的结果
compact方法
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
$username = 'houdun';
$ip = $request->userIP;
$arr = [
'age' => 7,
'class' => 5,
];
return $this->renderPartial('index', compact('username', 'ip', 'arr'));
}
}
xxs攻击
class HomeController extends Controller
{
public function actionIndex()
{
//这里模拟用户输入了一个script元素
$str = "<script>alert('haha');</script>";
return $this->renderPartial('index', ['str' => $str]);
}
}
viewshomeindex.php
<h1>hello</h1>
<h2><?php echo $str;?></h2>
出现了恶意弹窗,用以下的方法解决
conn
防止xss攻击
<?php
use yiihelpersHtml;
use yiihelpersHtmlPurifier;
?>
<!--//转码过滤,将html标签转成字符串-->
<?= Html::encode($str);?>
<!--//完全过滤,忽略html标签-->
<?= HtmlPurifier::process($str);?>
查看输出
模板继承
class HomeController extends Controller
{
//定义父模版
public $layout = 'home';
public function actionAbout()
{
return $this->render('about');
}
}
views/home/about.php
<h1>hello about</h1>
views/layouts/home.php
<html>
<body>
<h1>hello home</h1>
<?=$content; ?>
</body>
</html>
查看输出
模版相互调用
views/home/index.php
<h1>hello index</h1>
<?php echo $this->render('about');?>
查看输出
模型创建及数据读取
新建数据库
新建article数据表
id int类型
title varchar类型
加入一些记录
修改配置
config/db.php
return [
'class' => 'yiidbConnection',
'dsn' => 'mysql:host=localhost;dbname=yii',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
新建模型
models/Article
<?php
namespace appmodels;
use yiidbActiveRecord;
class Article extends ActiveRecord
{
//
}
使用
class HomeController extends Controller
{
public function actionIndex()
{
$sql = "select * from article where id=1";
$r = Article::findBySql($sql)->all();
dd($r);
}
}
查看返回
防止sql注入
使用客户端传递参数进行查询
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
$id = $request->get('id');
$sql = "select * from article where id=".$id;
$r = Article::findBySql($sql) -> all();
dd($r);
return $this->render('index');
}
}
上面这种方式十分危险,因为客户端可能传入恶意的sql语句
防止加料
使用替换占位符的方式传递查询参数
class HomeController extends Controller
{
public function actionIndex()
{
$request = Yii::$app->request;
$id = $request->get('id');
$sql = "select * from article where id=:id";
$r = Article::findBySql($sql, [':id' => $id]) -> all();
dd($r);
return $this->render('index');
}
}
浏览器输入http://hostname/index.php?r=home/index&id=3
where单表查询
class HomeController extends Controller
{
public function actionIndex()
{
//条件查询
// $data = Article::find()->where(['id' => 5])->all();
//指定范围
// $data = Article::find()->where(['>', 'id', 3])-> all();
//在2和5之间的
// $data = Article::find()->where(['between', 'id', 2, 5])-> all();
// 标题含有巴菲特
// $data = Article::find()->where(['like', 'title', '巴菲特'])-> all();
//只查询一条
// $data = Article::find()->where(['id' => 5])-> one();
//更快的方式
// $data = Article::findOne(5);
$data = Article::findAll([3, 4, 5]);
dd($data);
return $this->render('index');
}
}
节省内存
class HomeController extends Controller
{
public function actionIndex()
{
// find不加参数表示查询所有
// 转成数组,节省内存
$data = Article::find()->asArray()->all();
dd($data);
return $this->render('index');
}
}
查看
如果取的记录量大,一次性返回非常占内存
class HomeController extends Controller
{
public function actionIndex()
{
// 分多次取数据
foreach (Article::find()->batch(2) as $article) {
//看下每次输出几条
// echo count($article);
$data[] = $article;
}
dd($data);
return $this->render('index');
}
}
查看返回
此时返回了长度为3的二维数组,说明总共5条数据被分成3批返回
模型添加记录
在Article表中添加整型字段num
class HomeController extends Controller
{
public function actionIndex()
{
$article = new Article();
$article -> title = '这是新的一条记录';
$article -> num = 10;
// insert 方法可以进行插入,返回一个布尔值表示成功或失败
// $data = $article -> insert();
// seve 可以插入,也可以更新,返回一个布尔值表示成功或失败
$data = $article -> save();
//返回id属性方便进行后继操作
$id = $article->attributes['id'];
dd($id);
}
}
查看
模型修改记录
class HomeController extends Controller
{
public function actionIndex()
{
$article = Article::findOne(6);
$article -> title = '哈哈哈哈哈';
// update 更新操作,返回一个布尔值表示成功或失败
$data = $article->update();
// 同样的活save也能做
// $data = $article->save();
dd($data);
}
}
如果num代表查看次数,我们想每次访问时将其加1怎么操作
class HomeController extends Controller
{
public function actionIndex()
{
//updateAllCounters 参数
//1.要进行递增的字段和递增数
//2.更新的条件
//返回本次更新的记录条数
$data = Article::updateAllCounters(['num' => 1], ['id' => 6]);
dd($data);
}
}
模型删除记录
class HomeController extends Controller
{
public function actionIndex()
{
// 删除单条数据
// $article = Article::findOne(17);
// $article = Article::find()->where(['id' => 6 ])->one();
// $data = $article -> delete();
// all方法返回一个数组
// $article = Article::find()->where(['id' => 6 ])->all();
// $data = $article[0]->delete();
// 另一种方式
// 等号换成大小于号扩大查询范围
// 也可以写成id >:id And num<:num 使用多条件
$data = Article::deleteAll('id=:id', [':id' => 6]);
dd($data);
}
}
一对多查询
新建分类表category
为article 表新增一个表示分类的cate_id字段
新建分类模型
models/Category.php
class Category extends ActiveRecord
{
//
}
取分类下的文章
controllers/HomeController
class HomeController
{
public function actionAbout()
{
// 不推荐的方法
// 取到第一条分类信息
// 取分类下文章
// $category = Category::findOne(1);
// $articles = Article::find()->where(['cate_id'=> $category->attributes['id']])->all();
// 推荐方法
// className方法返回类的命名空间
// hasMany 参数
// 1.关联模型命名空间
// 2.关联条件['关联模型外键' => '当前模型主键']
$category = Category::findOne(1);
$articles = $category->hasMany(Article::className(), ['cate_id'=> 'id'])->all();
dd($articles);
}
}
一对多查询性能优化
将关联封装成模型的一个方法可以避免写重复的代码
controllers/HomeController
class HomeController
{
public function actionAbout()
{
$category = Category::findOne(2);
$articles = $category->getArticles();
dd($articles);
}
}
models/Article.php
class Category extends ActiveRecord
{
public function getArticles()
{
$articles = $this->hasMany(Article::className(), ['cate_id'=> 'id'])->asArray()->all();
return $articles;
}
}
另一种取法
controllers/HomeController
class HomeController
{
public function actionAbout()
{
$category = Category::findOne(2);
//注意这里使用的是模型属性,会调用模型的__get()找到对应的关联方法
$articles = $category->articles;
return $this->render('about');
}
}
class Category extends ActiveRecord
{
public function getArticles()
{
// 用属性调用时all方法会被隐式调用,不需要写在get方法中
$articles = $this->hasMany(Article::className(), ['cate_id'=> 'id'])->asArray();
return $articles;
}
}
一对一查询
controllers/HomeController
class HomeController
{
public function actionAbout()
{
$article = Article::findOne(1);
$category = $article->category;
dd($category);
}
}
models/Article
class Article
{
public function getCategory()
{
$category = $this->hasOne(Category::className(), ['id' => 'cate_id']);
return $category;
}
}
取多文章并带上对应的分类属性
controllers/HomeController
class HomeController
{
public function actionAbout()
{
//错误示例
// $articles = Article::find()->all();
// foreach ($articles as $article){
// $category[] = $article->category;
// }
// dd($category);
// 正确示范
$articles = Article::find()->with('category')->asArray()->all();
dd($articles);
}
}