MVC模式

发布时间 2024-01-07 15:26:41作者: xirang熙攘

内容

该部分内容可以让人快速的理解框架 的组成部分是如何封装的
目录分类 框架运行 sql封装 cookie原理 Session 简单的登录页面 图片操作扩展(验证码) 文件上传 smarty模板引擎 缓存
让人一步一步的去理解框架的组成

1.2 MVC介绍

1、MVC是一个编程思想,是一种设计模式

2、思想:将一个功能分解成3个部分,M V C

Model(模型):处理与数据有关的逻辑

View(视图):显示页面

Controller(控制器):处理业务逻辑

1561254292854

小结:

1、控制器用来接收请求

2、以后不能直接请求模型和视图

1.3 MVC演化

1.3.1 显示商品

1、导入products表的数据

2、将上一讲的MyPDO类拷贝到站点下,改名为MyPDO.class.php,这个文件中只存放MyPDO类

3、在站点下创建index.php,代码如下

<?php
//自动加载类
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});
//连接数据库
$param=array(
	'user'	=>	'root',
	'pwd'	=>	'root'
);
$mypdo= MyPDO::getInstance($param);
//获取商品数据
$list=$mypdo->fetchAll('select * from products');
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>无标题文档</title>
</head>

<body>
	<table border='1' width='980' bordercolor='#000'>
		<tr>
			<th>编号</th> <th>名称</th> <th>价格</th> <th>删除</th>
		</tr>
		<?php foreach($list as $rows):?>
		<tr>
			<td><?=$rows['proID']?></td>
			<td><?=$rows['proname']?></td>
			<td><?=$rows['proprice']?></td>
			<td><a href="">删除</a></td>
		</tr>
		<?php endforeach;?>
	</table>
</body>
</html>

运行结果

1561255129530

1.3.2 演化一:分离视图

1、创建products_list.html页面(视图页面),将显示部分的代码拷贝到视图页面上

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>无标题文档</title>
</head>

<body>
	<table border='1' width='980' bordercolor='#000'>
		<tr>
			<th>编号</th> <th>名称</th> <th>价格</th> <th>删除</th>
		</tr>
		<?php foreach($list as $rows):?>
		<tr>
			<td><?=$rows['proID']?></td>
			<td><?=$rows['proname']?></td>
			<td><?=$rows['proprice']?></td>
			<td><a href="">删除</a></td>
		</tr>
		<?php endforeach;?>
	</table>
</body>
</html>

2、在index.php页面上加载视图

<?php
//自动加载类
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});
//连接数据库
$param=array(
	'user'	=>	'root',
	'pwd'	=>	'root'
);
$mypdo= MyPDO::getInstance($param);
//获取商品数据
$list=$mypdo->fetchAll('select * from products');
//加载视图
require './products_list.html';

1.3.3 演化二:分离模型

模型的规则

1、一个表对应一个模型,表名和模型名一致(必须的)

2、模型以Model结尾(不是必须的)

代码实现:

1、在站点下创建ProductsModel.class.php页面

<?php
//products模型用来操作products表
class ProductsModel {
	//获取products表的数据
	public function getList() {
		//连接数据库
		$param=array(
			'user'	=>	'root',
			'pwd'	=>	'root'
		);
		$mypdo= MyPDO::getInstance($param);
		//获取商品数据
		return $mypdo->fetchAll('select * from products');
	}
}

2、在index.php页面中调用模型的getList()

<?php
//自动加载类
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});
//实例化模型
$model=new ProductsModel();
$list=$model->getList();
//加载视图
require './products_list.html';

1.3.4 演化三:分离基础模型

连接数据库的代码每个模型都要使用,所有我们需要将连接数据库的代码封装到基础模型类中(Model)

1561257239690

第一步:在站点下创建Model.class.php页面(基础模型)

<?php
//基础模型
class Model {
	protected $mypdo;
	public function __construct() {
		$this->initMyPDO();
	}
	//连接数据库
	private function initMyPDO() {
		$param=array(
			'user'	=>	'root',
			'pwd'	=>	'root'
		);
		$this->mypdo= MyPDO::getInstance($param);
	}
}

第二步:ProductsModel继承基础模型类

<?php
//products模型用来操作products表
class ProductsModel extends Model{
	//获取products表的数据
	public function getList() {
		return $this->mypdo->fetchAll('select * from products');
	}
}

1.3.5 演化四:分离控制器

控制器代码放在index.php页面中是不合理的,因为项目中的控制器会很多,而index.php只有一个。所以需要将控制器分离开来

控制器的规则:

1、一个模块对应一个控制器(必须的)

2、控制器以Controller结尾(不是必须的)

3、控制器中的方法以Action结尾(不是必须的),目的防止方法名是PHP关键字

创建ProductsController.class.php

<?php
//商品模块
class ProductsController {
	//获取商品列表
	public function listAction() {
		//实例化模型
		$model=new ProductsModel();
		$list=$model->getList();
		//加载视图
		require './products_list.html';
	}
}

index.php页面

<?php
//自动加载类
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});
//确定路由
$c=$_GET['c']??'Products';   //控制器
$a=$_GET['a']??'list';		//方法
$c=ucfirst(strtolower($c));		//首字母大写
$a=strtolower($a);				//转成小写
$controller_name=$c.'Controller';	//拼接控制器类名
$action_name=$a.'Action';	//拼接方法名
//请求分发
$obj=new $controller_name();
$obj->$action_name();

1561260190738

通过在url地址上传递参数来寻址。

c:控制器

a:方法

1561261105995

注意:每次请求都要从index.php进入。所以index.php又叫入口文件。

小结:

1561261460012

1.4 删除商品

入口(products_list.html)

<td><a href="index.php?c=Products&a=del&proid=<?=$rows['proID']?>" onclick="return confirm('确定要删除吗')">删除</a></td>

控制器(ProductsController)

<?php
//商品模块
class ProductsController {
	..
	//删除商品
	public function delAction() {
		$id=(int)$_GET['proid'];	//如果参数明确是整数,要强制转成整形
		$model=new ProductsModel();
		if($model->del($id))
			header('location:index.php?c=Products&a=list');
		else {
			echo '删除失败';
			exit;
		}
	}
}

模型(ProductsModel)

<?php
//products模型用来操作products表
class ProductsModel extends Model{
	...
	//删除商品
	public function del($proid) {
		return $this->mypdo->exec("delete from products where proid=$proid");
	}
}

2.2 框架目录

2.2.1 创建目录结构

1561428443831

2.2.2 文件分类存放

将上一讲的文件分类存放到不同的目录中

1561429370394

将文件存放到不同的目录以后,由于类文件地址发生了变化,所以无法完成自动加载类,那么今天的主要任务就是围绕如何实现类的自动加载展开。

由于每次都请求入口文件,所以”.“表示入口文件所在的目录

2.3 添加命名空间

通过文件目录地址做命名空间,这样获取了命名空间就能知道文件存放的地址。

Model.class.php

namespace Core;
class Model {
    ...

MyPDO.class.php

namespace Core;
class MyPDO{
    ...

ProductsModel.class.php

<?php
namespace Model;
//products模型用来操作products表
class ProductsModel extends Model{
    ...

ProductsController.class.php

<?php
namespace Controller\Admin;
//商品模块
class ProductsController {
    ...

2.4 框架类实现

2.4.1 定义路径常量

由于文件路径使用频率很高,而且路径比较长,所以将固定不变的路径定义成路径常量

知识点

1、getcwd():入口文件的绝对路径
2、windows下默认的目录分隔符是`\`,Linux下默认的目录分隔符是`/`。DIRECTORY_SEPARATOR常量根据不同的操作系统返回不同的目录分隔符。

代码实现

在Core文件夹下创建Framework.class.php

private static function initConst(){
    define('DS', DIRECTORY_SEPARATOR);  //定义目录分隔符
    define('ROOT_PATH', getcwd().DS);  //入口文件所在的目录
    define('APP_PATH', ROOT_PATH.'Application'.DS);   //application目录
    define('CONFIG_PATH', APP_PATH.'Config'.DS);
    define('CONTROLLER_PATH', APP_PATH.'Controller'.DS);
    define('MODEL_PATH', APP_PATH.'Model'.DS);
    define('VIEW_PATH', APP_PATH.'View'.DS);
    define('FRAMEWORK_PATH', ROOT_PATH.'Framework'.DS);
    define('CORE_PATH', FRAMEWORK_PATH.'Core'.DS);
    define('LIB_PATH', FRAMEWORK_PATH.'Lib'.DS);
    define('TRAITS_PATH', ROOT_PATH.'Traits'.DS);
}

2.4.2 引入配置文件

1、在config目录下创建config.php

<?php
return array(
    //数据库配置
    'database'=>array(),
    //应用程序配置
    'app'       =>array(
        'dp'    =>  'Admin',        //默认平台
        'dc'    =>  'Products',     //默认控制器
        'da'    =>  'list'          //默认方法
    ),
);

2、在框架类中引入配置文件

private static function initConfig(){
   $GLOBALS['config']=require CONFIG_PATH.'config.php';
}

思考:配置文件为什么不保存在常量中?

答:因为7.0之前,常量不能保存数组和对象。

2.4.3 确定路由

p:【platform】平台

c:【controller】控制器

a:【action】方法

1561431781576

private static function initRoutes(){
    $p=$_GET['p']??$GLOBALS['config']['app']['dp'];
    $c=$_GET['c']??$GLOBALS['config']['app']['dc'];
    $a=$_GET['a']??$GLOBALS['config']['app']['da'];
    $p=ucfirst(strtolower($p));
    $c=ucfirst(strtolower($c));		//首字母大写
    $a=strtolower($a);			//转成小写
    define('PLATFROM_NAME', $p);    //平台名常量
    define('CONTROLLER_NAME', $c);  //控制器名常量
    define('ACTION_NAME', $a);      //方法名常量
    define('/index.php?s=-news', CONTROLLER_PATH.$p.DS);   //当前请求控制器的目录地址
    define('__VIEW__',VIEW_PATH.$p.DS);     //当前视图的目录地址
}

2.4.4 自动加载类

private static function initAutoLoad(){
    spl_autoload_register(function($class_name){
        $namespace= dirname($class_name);   //命名空间
        $class_name= basename($class_name); //类名
        if(in_array($namespace, array('Core','Lib')))   //命名空间在Core和Lib下
            $path= FRAMEWORK_PATH.$namespace.DS.$class_name.'.class.php';
        elseif($namespace=='Model')     //文件在Model下
            $path=MODEL_PATH.$class_name.'.class.php';
        elseif($namespace=='Traits')    //文件在Traits下
            $path=TRAITS_PATH.$class_name.'.class.php';
        else   //控制器
            $path=CONTROLLER_PATH.PLATFROM_NAME.DS.$class_name.'.class.php'; 
        if(file_exists($path) && is_file($path))
            require $path;
    });
}

2.4.5 请求分发

private static function initDispatch(){
    $controller_name='\Controller\\'.PLATFROM_NAME.'\\'.CONTROLLER_NAME.'Controller';	//拼接控制器类名
    $action_name=ACTION_NAME.'Action';	//拼接方法名
    $obj=new $controller_name();
    $obj->$action_name();
} 

2.4.6 封装run()方法

class Framework{
    //启动框架
    public static function run(){
        self::initConst();
        self::initConfig();
        self::initRoutes();
        self::initAutoLoad();
        self::initDispatch();
    }
    ...

2.4.7 在入口中调用run()方法

<?php
require './Framework/Core/Framework.class.php';
Framework::run();

run()方法调用后就启动了框架。

2.5 运行项目

1、连接数据库的参数从配置文件中获取

class Model {
   ...
   //连接数据库
   private function initMyPDO() {
      $this->mypdo= MyPDO::getInstance($GLOBALS['config']['database']);
   }
}

2、更改ProductsControleller控制器

<?php
namespace Controller\Admin;
//商品模块
class ProductsController {
	//获取商品列表
	public function listAction() {
            //实例化模型
            $model=new \Model\ProductsModel();
            $list=$model->getList();
            //加载视图
            require __VIEW__.'products_list.html';
	}
	//删除商品
	public function delAction() {
		...
		$model=new \Model\ProductsModel();
		...
	}
}

3、更改ProductsModel类

<?php
namespace Model;
class ProductsModel extends \Core\Model{
	
}

4、更改MyPDO类

...
private function fetchType($type){
    switch ($type){
        case 'num':
            return \PDO::FETCH_NUM;
        case 'both':
            return \PDO::FETCH_BOTH;
        case 'obj':
            return \PDO::FETCH_OBJ;
        default:
             return \PDO::FETCH_ASSOC;
    }
}
...
//所有的内置类都在公共的命名空间下。

测试:成功

2.6 traits代码复用

有的控制器操作完毕后要跳转,有的不需要,

解决:将跳转的方法封装到traits中。

代码实现

1、将准备好的图片拷贝到Public目录下

1561446087858

2、在Traits目录中创建Jump.class.php

<?php
//跳转的插件
namespace Traits;
trait Jump{
    //封装成功的跳转
    public function success($url,$info='',$time=1){
        $this->redirect($url, $info, $time, 'success');
    }
    //封装失败跳转
    public function error($url,$info='',$time=3){
        $this->redirect($url, $info, $time, 'error');
    }
    /*
     * 作用:跳转的方法
     * @param $url string 跳转的地址
     * @param $info string 显示信息
     * @param $time int 停留时间
     * @param $flag string 显示模式  success|error
     */
    private function redirect($url,$info,$time,$flag){
        if($info=='')
            header ("location:{$url}");
        else{
          echo <<<str
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
    <!--
    <meta http-equiv="refresh" content="3;http://www.php.com"/>
    -->
	<title>Document</title>
<style>
body{
	text-align: center;
	font-family: '微软雅黑';
	font-size: 18px;
}
#success,#error{
	font-size: 36px;
	margin: 10px auto;
}
#success{
	color: #090;
}
#error{
	color: #F00;
}
</style>
</head>
<body>
	<img src="/Public/images/{$flag}.fw.png">
	<div id='{$flag}'>{$info}</div>
	<div><span id='t'>{$time}</span>秒以后跳转</div>
</body>
</html>
<script>
window.onload=function(){
	var t={$time};
	setInterval(function(){
		document.getElementById('t').innerHTML=--t;
		if(t==0)
			location.href='index.php';
	},1000)
}
</script>
str;
        exit;
        }
    }
}

在ProductsController控制器中使用原型

namespace Controller\Admin;
//商品模块
class ProductsController{
    use \Traits\Jump;   //复用代码
    ...

2.7 删除功能

入口

<a href="index.php?p=Admin&c=Products&a=del&proid=<?=$rows['proID']?>" onclick="return confirm('确定要删除吗')">删除</a>

控制器(ProductsController)

public function delAction() {
   $id=(int)$_GET['proid'];	//如果参数明确是整数,要强制转成整形
   $model=new \Model\ProductsModel();
   if($model->del($id))
       $this->success('index.php?p=Admin&c=Products&a=list', '删除成功');
   else 
       $this->error('index.php?p=admin&c=Products&a=list', '删除失败');
}

3.2 SQL封装

每个功能都要写SQL语句,我们可以封装一个万能的方法来操作所有的表

3.2.1 生成insert语句

<?php
$table='products';	//表名
//插入的数据
$data['proid']='111';
$data['proname']='钢笔';
$data['proprice']=120;
//第一步:拼接字段名
$keys=array_keys($data);		//获取所有的字段名
$keys=array_map(function($key){	//在所有的字段名上添加反引号
	return "`{$key}`";
},$keys);
$keys=implode(',',$keys);		//字段名用逗号连接起来

//第二步:拼接值
$values=array_values($data);	//获取所有的值
$values=array_map(function($value){	//所有的值上添加单引号
	return "'{$value}'";
},$values);
$values=implode(',',$values);	//值通过逗号连接起来

//第三步:拼接SQL语句

echo $sql="insert into `{$table}` ($keys) values ($values)";

小结:

1、array_keys:获取数组的键

2、array_values:获取数组的值

3、array_map():数组中的每一个元素依次调用回调函数。

3.2.2 生成更新语句

<?php
$table='products';	//表名
$data['proname']='钢笔';
$data['proprice']=120;
$data['proID']='111';
//获取主键
function getPrimaryKey($table) {
	//连接数据库
	$link=mysqli_connect('localhost','root','root','data');
	mysqli_set_charset($link,'utf8');
	//查看表结构
	$rs=mysqli_query($link,"desc `{$table}`");
	//循环判断主键
	while($rows=mysqli_fetch_assoc($rs)){
		if($rows['Key']=='PRI')
			return $rows['Field'];
	}
}
//第一步:获取非主键
$keys=array_keys($data);	//获取所有键
$pk=getPrimaryKey($table);	//获取主键
$index=array_search($pk,$keys);	//返回主键在数组中的下标
unset($keys[$index]);		//删除主键
//第二步:拼接`键`='值'的形式
$keys=array_map(function($key) use ($data){
	return "`{$key}`='{$data[$key]}'";
},$keys);
$keys=implode(',',$keys);
//第三步:拼接SQL语句
echo $sql="update `{$table}` set $keys where $pk='{$data[$pk]}'";

3.2.3 生成select语句

<?php
/**
*$table string 表名
*$cond array 条件
*/
function select($table,$cond=array()) {
	$sql="select * from `{$table}` where 1";
	//拼接条件
	if(!empty($cond)){
		foreach($cond as $k=>$v){
			if(is_array($v)){	//条件的值是数组类型
				switch($v[0]){	//$v[0]保存的是符号,$v[1]是值
					case 'eq':		//等于  equal
						$op='=';
						break;
					case 'gt':		//大于  greater than
						$op='>';
						break;
					case 'lt':
						$op='<';
						break;
					case 'gte':
					case 'egt':
						$op='>=';
						break;
					case 'lte':
					case 'elt':
						$op='<=';
						break;
					case 'neq':
						$op='<>';
						break;
				}
				$sql.=" and `$k` $op '$v[1]'";
			}else{
				$sql.=" and `$k`='$v'";
			}
		}
	}

	return $sql;
}

//测试
$table='products';	//表名
$cond=array(
	'proname'	=>	'钢笔',
	'proprice'	=>	array('eq','12'),
	'aa'	=>	array('gt',10),
	'bb'	=>	array('lt',20),
);

echo select($table),'<br>';
echo select($table,$cond);

3.2.4 获取表名

<?php
namespace Core;
class Model {
	private $table;
	public function __construct($table='') {
		if($table!='')		//直接给基础模型传递表名
			$this->table=$table;
		else {				//实例化子类模型
			$this->table=substr(basename(get_class($this)),0,-5);
		}

		echo $this->table,'<br>';
	}
}

namespace Model;
class ProductsModel extends \Core\Model{
	
}
namespace Controller\Admin;

new \Core\Model('news');			//news
new \Model\ProductsModel();			//Products

小结:

1、get_class():获取对象的类(包括命名空间)

2、substr():截取字符串,-5表示字符串的最后5个字符忽略

3.2.5 在项目中封装万能的增、删、改、查

由于封装的方法可以操作所有的表,可以这些方法封装在基础模型中

<?php
namespace Core;
//基础模型
class Model {
    protected $mypdo;
    private $table; //表名
    private $pk;    //主键
    public function __construct($table='') {
        $this->initMyPDO();
        $this->initTable($table);
        $this->getPrimaryKey();
    }
    //连接数据库
    private function initMyPDO() {
        $this->mypdo= MyPDO::getInstance($GLOBALS['config']['database']);
    }
    //获取表名
    private function initTable($table){
        if($table!='')		//直接给基础模型传递表名
            $this->table=$table;
        else {				//实例化子类模型
            $this->table=substr(basename(get_class($this)),0,-5);
        }
    }
    //获取主键
    private function getPrimaryKey() {
	$rs=$this->mypdo->fetchAll("desc `{$this->table}`");
	foreach($rs as $rows){
            if($rows['Key']=='PRI'){
                $this->pk=$rows['Field'];
                break;
            }
	}
    }
    //万能的插入
    public function insert($data){
        $keys=array_keys($data);		//获取所有的字段名
        $keys=array_map(function($key){	//在所有的字段名上添加反引号
                return "`{$key}`";
        },$keys);
        $keys=implode(',',$keys);		//字段名用逗号连接起来
        $values=array_values($data);	//获取所有的值
        $values=array_map(function($value){	//所有的值上添加单引号
                return "'{$value}'";
        },$values);
        $values=implode(',',$values);	//值通过逗号连接起来
        $sql="insert into `{$this->table}` ($keys) values ($values)";
        return $this->mypdo->exec($sql);
    }
    //万能的更新
    public function update($data){
        $keys=array_keys($data);	//获取所有键
        $index=array_search($this->pk,$keys);	//返回主键在数组中的下标
        unset($keys[$index]);		//删除主键
        $keys=array_map(function($key) use ($data){
                return "`{$key}`='{$data[$key]}'";
        },$keys);
        $keys=implode(',',$keys);
        $sql="update `{$this->table}` set $keys where $this->pk='{$data[$this->pk]}'";
        return $this->mypdo->exec($sql);
    }
    //删除
    public function delete($id){
        $sql="delete from `{$this->table}` where `{$this->pk}`='$id'";
        return $this->mypdo->exec($sql);
    }
    //查询,返回二维数组
    public function select($cond=array()){
        $sql="select * from `{$this->table}` where 1";
	if(!empty($cond)){
            foreach($cond as $k=>$v){
                if(is_array($v)){	//条件的值是数组类型
                        switch($v[0]){	//$v[0]保存的是符号,$v[1]是值
                                case 'eq':		//等于  equal
                                        $op='=';
                                        break;
                                case 'gt':		//大于  greater than
                                        $op='>';
                                        break;
                                case 'lt':
                                        $op='<';
                                        break;
                                case 'gte':
                                case 'egt':
                                        $op='>=';
                                        break;
                                case 'lte':
                                case 'elt':
                                        $op='<=';
                                        break;
                                case 'neq':
                                        $op='<>';
                                        break;
                        }
                        $sql.=" and `$k` $op '$v[1]'";
                }else{
                        $sql.=" and `$k`='$v'";
                }
            }
	}
        return $this->mypdo->fetchAll($sql);
    }
    //查询,返回一维数组
    public function find($id){
        $sql="select * from `{$this->table}` where `{$this->pk}`='$id'";
        return $this->mypdo->fetchRow($sql);
    }
}

3.2.6 更改项目

1、删除ProductsModel类中的方法

<?php
namespace Model;
//products模型用来操作products表
class ProductsModel extends \Core\Model{
}

2、在控制器中直接调用基础模型的方法

class ProductsController{
    use \Traits\Jump;
    //获取商品列表
    public function listAction() {
        //实例化模型
        $model=new \Model\ProductsModel();
        $list=$model->select();
        //加载视图
        require __VIEW__.'products_list.html';
    }
    //删除商品
    public function delAction() {
        $id=(int)$_GET['proid'];	//如果参数明确是整数,要强制转成整形
        $model=new \Model\ProductsModel();
        if($model->delete($id))
            $this->success('index.php?p=Admin&c=Products&a=list', '删除成功');
        else 
            $this->error('index.php?p=admin&c=Products&a=list', '删除失败');
    }
    ...

3.3 作业实现

3.3.1 添加商品

步骤:

1、创建添加商品页面

2、实现添加逻辑

代码实现

1、入口(products_list.html)

<a href="index.php?p=Admin&c=Products&a=add">添加商品</a>

2、控制器(ProductsController)

public function addAction(){
    //执行添加逻辑
    if(!empty($_POST)){
        $model=new \Core\Model('products');
        if($model->insert($_POST))
            $this->success ('index.php?p=Admin&c=Products&a=list', '插入成功');
        else
            $this->error ('index.php?p=Admin&c=Products&a=add', '插入失败');
    }
    //显示添加页面
    require __VIEW__.'products_add.html';
}

3、模型

4、视图:在view\Admin目录下创建products_add.html页面

<body>
<form method="post" action="">
	名称: <input type="text" name="proname"> <br />
	价格: <input type="text" name="proprice"> <br />
	<input type="submit" value="提交">
</form>
</body>

3.3.2 修改商品

步骤:

1、显示修改的界面

2、执行修改逻辑

代码实现

1、入口(products_list.html)

<a href="index.php?p=Admin&c=Products&a=edit&proid=<?=$rows['proID']?>">修改</a>

2、控制器(ProductsController)

public function editAction(){
    $proid=$_GET['proid'];  //需要修改的商品id
    $model=new \Core\Model('products');
    //执行修改逻辑
    if(!empty($_POST)){
        $_POST['proID']=$proid;
        if($model->update($_POST))
            $this->success ('index.php?p=Admin&c=Products&a=list', '修改成功');
        else
            $this->error ('index.php?p=Admin&c=Products&a=edit&proid='.$proid, '修改失败');
    }
    //显示商品
    $info=$model->find($proid);
    require __VIEW__.'products_edit.html';
}

3、模型

4、视图:在view\admin目录下创建products_edit.html

<body>
<form method="post" action="">
	名称: <input type="text" name="proname" value='<?=$info['proname']?>'> <br />
	价格: <input type="text" name="proprice" value='<?=$info['proprice']?>'> <br />
	<!--
	<input type="hidden" name="proID" value=<?=$info['proID']?>>
	-->
	<input type="submit" value="提交">
</form>

思考:A页面中的变量如果提供给B页面访问

方法一:包含文件

方法二:get或post提交

方法三:cookie,cookie就是保存在客户端的信息文件

3.4.1 原理

cookie是保存在客户端的信息包(一个文件)

1561618717872

通过header()、setcookie()操作响应头

语法格式:header(键:值)

<?php
header('content-type:charset=gbk');
header('name:tom');

setcookie()作用:将值放到响应头中发送到客户端,并保存到客户端。

3.4.2 设置cookie

<?php
setcookie('name','tom');	//将name=tom放到响应头中

在响应头中可以看到cookie的信息

1561619494663

客户端有cookei信息后,每次请求服务器,cookie的信息都会自动的放到请求头中带到服务器。

1561619527214

3.4.3 获取cookie的值

<?php
echo $_COOKIE['name'];	//从请求头中获取名字是name的cookie

注意:

1、关闭浏览器后,cookie消失。这种cookie称为临时性cookie

2、cookie的信息不可以在不同的浏览器中共享,不可以跨浏览器。

思考:如下代码为什么第一次执行报错,第二次执行正常

<?php
setcookie('name','tom');
echo $_COOKIE['name'];  //在请求头中获取name的cookie

因为:第一次访问请求头中没有cookie的值所以获取不到,第二次访问由于第一次已经设置了将cookie设置到响应头中,第二次访问就会自动将cookie的信息放到请求头中,所以第二次访问就能获取cookie的值了

3.4.4 永久性cookie

说明:关闭浏览器后cookie的值不消失

应用场景:

1554000322182

语法:给cookie添加过期时间就形成了永久性cookie,过期时间是时间类型是时间戳

$time=time()+3600;
setcookie('name','tom',$time);	 //cookie的有效时间是3600秒

3.4.5 cookie的有效目录

cookie默认在当前目录及子目录中有效

cookie一般要设置在整站有效

setcookie('name','tom',0,'/');	 //   /表示根目录

3.4.6 支持子域名

场景:每个域名代码一个网站,网站之间的cookie是不可以相互访问的。

问题:百度下有多个二级域名的网站,他们自己的cookie是要共享的,如何实现?

<?php
setcookie('name','tom',0,'/','baidu.com');   //在baidu.com域名下都有效
?>
<a href="http://www.bb.baidu.com/bb.php">跳转</a>

3.4.7 是否安全传输

安全传输就是https传输。

默认情况下https和http都可以传输cookie

setcookie('name','tom',0,'/','',true);	 //   true表示只能是https传输

3.4.8 是否安全访问

默认情况下,PHP和JS都可以访问cookie

安全访问:PHP可以访问,JS不可以 默认是false。

php代码

<?php
setcookie('name','tom',0,'/','',false,true);	
?>
<a href="/5-demo2.php">跳转</a>

html代码

<?php
echo $_COOKIE['name'],'<br>';     //PHP获取cookie
?>

<script type="text/javascript">
	document.write(document.cookie);  //js获取cookie
</script>

3.4.9 删除cookie

注意:cookie中只能保存数字和字符串。

<?php
//setcookie('name',false);			//删除cookie方法一
//setcookie('name');				//删除cookie方法二
setcookie('name','tom',time()-1);	//删除cookie方法三

3.4.10 cookie的缺点

1、因为在浏览器中可以看到cookie 的值,所以安全性低

2、因为只能保存字符串和数字,所以可控性差

3、因为数据放在请求头中传输,增加了请求时候的数据负载。

4、因为数据存储在浏览器中,但浏览器存储空间是有吸限制的,一般是4K。

4.2 Session(会话)

4.2.1 原理

1、session是服务器端的技术

2、session是基于cookie技术的

1561687448156

4.2.2 session操作

1、默认情况下,会话不会自动开启,通过session_start()开启会话

2、通过session_id()获取会话的编号

3、通过$_SESSION操作会话

4、会话可以保存除了资源以外的所有类型。

5、重复开启会话会报错,一般出现在包含文件中。

<?php
session_start();		//开启会话
@session_start();      //重复开启会话会报错,可以通过错误抑制符来屏蔽错误
$_SESSION['name']='tom';	//保存会话
$_SESSION['age']=20;

echo $_SESSION['name'],'<br>';
echo $_SESSION['age'],'<br>';
echo '会话编号:'.session_id();   //获取会话编号

session_start()作用

1、没有会话空间就创建一个空间
2、有会话空间就打开空间

4.2.3 与会话有关的配置

重要:

1、session.save_path="F:\wamp\tmp\"		session保存的地址
2、session.auto_start = 1				session自动开启,默认不自动开启
3、session.save_handler = files			会话以文件的形式保存
4、session.gc_maxlifetime = 1440			会话的生命周期是1440秒

了解:

session.name = PHPSESSID
session.cookie_lifetime = 0				会话编号的过期时间
session.cookie_path = /					会话编号整站有效
session.cookie_domain =					会话编号在当前域名下有效

4.2.4 销毁会话

通过session_destroy()销毁会话

销毁会话就是删除自己的会话文件。

<?php
session_start();
session_destroy();	//销毁会话

4.2.5 垃圾回收

1、会话文件超过了生命周期是垃圾文件。

2、PHP自动进行垃圾回收

3、垃圾回收的概率默认是1/1000

session.gc_probability = 1
session.gc_divisor = 1000

4.2.6 session和cookie的区别

cookie session
保存位置 客户端 服务器端
数据大小 小(4K)
数据类型 字符串 除了资源以外的所有类型
安全性 不安全 安全

4.2.7 禁用cookie对session的影响

session是基于cookie的,如果禁用cookie,session无法使用。

1561692785732

解决:

默认情况下,session只依赖于cookie,session的编号只能通过cookie传输

可以设置为session不仅仅依赖于cookie

session.use_only_cookies = 0    // session不仅仅依赖于cookie
session.use_trans_sid = 1		//允许通过其他方式传递session_id

设置后,php自动添加get和post传递session_id

1561693334769

4.3 session入库

session默认情况下存储到文件中,我们可以将session存储到数据库中

4.3.1 创建sess表

-- 如果用了text类型就不能使用memory引擎
drop table if exists sess;
create table sess(
       sess_id varchar(50) primary key comment '会话编号',
       sess_value text comment '会话值',
       sess_time int unsigned not null comment '会话产生时间'
)engine=innodb  charset=utf8 comment '会话表'

-- memory引擎数据存储在内存中
drop table if exists sess;
create table sess(
       sess_id varchar(50) primary key comment '会话编号',
       sess_value varchar(2000) comment '会话值',
       sess_time int unsigned not null comment '会话产生时间'
)engine=memory  charset=utf8 comment '会话表'

memory引擎的注意事项

1、memory引擎数据存储在内存中,速度快,但是重启服务后数据清空

2、memory引擎中的字段不可以是text类型

4.3.2 更改会话存储(session入库)

1、知识点

a)通过session_set_save_handler()更改存储
b)session_set_save_handler()必须在session_start()之前
c)有6个回调函数,open,close,read,write,destroy,gc。
4)read必须返回字符串,其他函数返回bool值

6个回调函数执行的时间:
open():开启会话执行
close():关闭会话执行
read():打开会话后就执行
write():更改会话会话的值和关闭会话之前执行,如果调用了session_destroy()就不会调用write()
destroy():调用session_destroy()的时候自动执行
gc():垃圾回收的时候自动执行。

2、代码实现

<?php
//打开会话
function open() {
	global $link;
	$link=mysqli_connect('localhost','root','root','data');
	mysqli_set_charset($link,'utf8');
	return true;
}
//关闭会话
function close() {
	return true;
}
//读取会话
function read($sess_id) {
	global $link;
	$sql="select sess_value from sess where sess_id='$sess_id'";
	$rs=mysqli_query($link,$sql);
	$rows=mysqli_fetch_row($rs);
	return (string)$rows[0];
}
//写入会话
function write($sess_id,$sess_value) {
	global $link;
	$sql="insert into sess values ('$sess_id','$sess_value',unix_timestamp()) on duplicate key update sess_value='$sess_value',sess_time=unix_timestamp()";
	return mysqli_query($link,$sql);
}
//销毁会话
function destroy($sess_id) {
	global $link;
	$sql="delete from sess where sess_id='$sess_id'";
	return mysqli_query($link,$sql);
}
//垃圾回收
function gc($lifetime) {
	global $link;
	$expires=time()-$lifetime;	//过期时间点
	$sql="delete from sess where sess_time<$expires";
	return mysqli_query($link,$sql);
}
//更改会话存储
session_set_save_handler('open','close','read','write','destroy','gc');
//开启会话
session_start();
//session_destroy();

4.3.3 项目封装

1、在Lib目录下创建Session.class.php页面

<?php
namespace Lib;
class Session{
    private $mypdo;
    public function __construct() {
        session_set_save_handler(
            [$this,'open'],
            [$this,'close'],
            [$this,'read'],
            [$this,'write'],
            [$this,'destroy'],
            [$this,'gc']
        );
        session_start();
    }
    public function open() {
        $this->mypdo= \Core\MyPDO::getInstance($GLOBALS['config']['database']);
        return true;
    }
    //关闭会话
    public function close() {
        return true;
    }
    //读取会话
    public function read($sess_id) {
        $sql="select sess_value from sess where sess_id='$sess_id'";
        return (string)$this->mypdo->fetchColumn($sql);
    }
    //写入会话
    public function write($sess_id,$sess_value) {
        $sql="insert into sess values ('$sess_id','$sess_value',unix_timestamp()) on duplicate key update sess_value='$sess_value',sess_time=unix_timestamp()";
        return $this->mypdo->exec($sql)!==false;
    }
    //销毁会话
    public function destroy($sess_id) {
        $sql="delete from sess where sess_id='$sess_id'";
        return $this->mypdo->exec($sql)!==false;
    }
    //垃圾回收
    public function gc($lifetime) {
        $expires=time()-$lifetime;	//过期时间点
        $sql="delete from sess where sess_time<$expires";
        return $this->mypdo->exec($sql)!==false;
    }
}

2、由于需要启动项目的时候就开启会话存储,将session入库的调用放在基础控制器中。在Core目录下创建Controller.class.php(基础控制器)

<?php
//基础控制器
namespace Core;
class Controller{
    public function __construct() {
        $this->initSession();
    }
    //初始化session
    private function initSession(){
        new \Lib\Session();
    }
}

3、所有的控制器都继承基础控制器

<?php
namespace Controller\Admin;
//商品模块
class ProductsController extends \Core\Controller{
    ...

测试结果:只要访问控制器就会启动session入库。

4.4 登录模块

4.4.1 创建用户表

drop table if exists `user`;
create table `user`(
       user_id smallint unsigned auto_increment primary key comment '主键',
       user_name varchar(20) not null comment '用户名',
       user_pwd char(32) not null comment '密码',
       user_face varchar(50) comment '头像',
       user_login_ip int comment '最后登录的IP',
       user_login_time int unsigned comment '最后登录的时间',
       user_login_count smallint unsigned default 0 comment '登录次数',
       is_admin tinyint default 0 comment '超级管理员'
)engine=innodb charset=utf8 comment '用户表';

4.4.2 显示界面

1、将HTML模板页面拷贝到View\Admin目录下

2、将images、css 拷贝到Public\Admin目录下

显示登录、注册界面

1、在Controller\Admin目录下创建LoginController.class.php

<?php
namespace Controller\Admin;
use Core\Controller;    //引入基础控制器
class LoginController extends Controller{
    //登录
    public function loginAction(){
        require __VIEW__.'login.html';
    }
    //注册
    public function registerAction(){
        require __VIEW__.'register.html';
    }
}

2、更改login.html、register.html页面中的静态资源路径

    <link rel="stylesheet" href="/Public/Admin/css/pintuer.css">
    <link rel="stylesheet" href="/Public/Admin/css/admin.css">

注意:在HTML中路径要使用绝对路径,从根目录开始匹配

​ 在CSS页面图片的路径要使用相对路径,相对于当前页面本身。

3、将login.html、register.html页面联通起来

-- login.html跳转到register.html
<input type="button" value="用户注册"  class="button button-block bg-main text-big" onClick="location.href='index.php?p=Admin&c=Login&a=register'" />

-- register.html跳转到login.html
<input type="button"  class="button button-block bg-main text-big" value="返回" onClick="location.href='index.php?p=Admin&c=Login&a=login'" />

显示后台管理界面

1、在Controller\Admin目录下创建AdminController.class.php

<?php
namespace Controller\Admin;
class AdminController extends \Core\Controller{
    public function adminAction(){
        require __VIEW__.'admin.html';
    }
    public function topAction(){
        require __VIEW__.'top.html';
    }
    public function menuAction(){
        require __VIEW__.'menu.html';
    }
    public function mainAction(){
        require __VIEW__.'main.html';
    }
}

2、在admin.html中,更改框架集中的路径

<frameset rows="95,*" cols="*" frameborder="no" border="0" framespacing="0">
  <frame src="index.php?p=Admin&c=Admin&a=top" name="topFrame" scrolling="no" noresize="noresize" id="topFrame" />
  <frameset rows="*" cols="180,*" framespacing="0" frameborder="no" border="0">
    <frame src="index.php?p=Admin&c=Admin&a=menu" name="leftFrame" scrolling="no" noresize="noresize" id="leftFrame" />
    <frame src="index.php?p=Admin&c=Admin&a=main" name="mainFrame" id="mainFrame" />
  </frameset>
</frameset>

3、更改top.html、menu.html、main.html的静态资源

测试

1561710621113

4.4.3 用户注册

配置文件

'app'       =>array(
        'key'   =>  'itcast',       //加密秘钥

控制器(LoginController)

public function registerAction(){
    //第二步:执行注册逻辑
    if(!empty($_POST)){
        $data['user_name']=$_POST['username'];
        $data['user_pwd']=md5(md5($_POST['password']).$GLOBALS['config']['app']['key']);
        $model=new \Core\Model('user');
        if($model->insert($data))
            $this->success ('index.php?p=Admin&c=Login&a=login', '注册成功,您可以去登陆了');
        else
            $this->error ('index.php?p=Admin&c=Login&a=register', '注册失败,请重新注册');
    }     
    //第一步:显示注册界面
    require __VIEW__.'register.html';
}

模型

视图

多学一招:md5的单向加密,md5解密使用的是数据字典实现。

4.4.4 完善注册功能

用户名是不能重复的,但输入用户名以后,通过异步判断一下此用户名是否存在。

1、ajax代码

<script>
window.onload=function(){
    var req=new XMLHttpRequest();   //创建ajax对象
    document.getElementById('username').onblur=function(){
        document.getElementById('msg').innerHTML='';
        req.open('get','/index.php?p=admin&c=Login&a=checkUser&username='+this.value);
        req.onreadystatechange=function(){
            if(req.readyState==4 && req.status==200){
                if(req.responseText=='1'){
                    document.getElementById('msg').innerHTML='用户名已经存在';
               }
            }
        }
        req.send();
        
    }
}
</script>

...
<div class="field field-icon-right">
<input type="text" class="input" name="username" placeholder="请输入用户名" id='username' />
<span id='msg'></span>
 </div>

2、控制器(LoginController)

public function checkUserAction(){
    $model=new \Model\UserModel();
    echo $model->isExists($_GET['username']);
}

3、模型(UserModel)

<?php
namespace Model;
class UserModel extends \Core\Model{
    //用户存在返回1,否则返回0
    public function isExists($name){
        $info=$this->select(['user_name'=>$name]);
        return empty($info)?0:1;
    }
}

1561716144848

4.4.5 用户登陆

原理:通过用户名和密码找到对应的用户就是登陆成功

控制器(LoginController)

namespace Controller\Admin;
use Core\Controller;    //引入基础控制器
class LoginController extends Controller{
    //登录
    public function loginAction(){
        //第二步:执行登陆逻辑
        if(!empty($_POST)){
            $model=new \Model\UserModel();
           if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
                $this->success('index.php?p=Admin&c=Admin&a=admin', '登陆成功');
            }else{
                $this->error('index.php?p=Admin&c=Login&a=login', '登陆失败,请重新登陆');
            }
        }        
        //第一步:显示登陆界面
        require __VIEW__.'login.html';
    }
    ...

模型(UserModel)

//通过用户名和密码获取用户的信息
public function getUserByNameAndPwd($name,$pwd){
    //条件数组
    $cond=array(
        'user_name'  =>  $name,
        'user_pwd'   => md5(md5($pwd).$GLOBALS['config']['app']['key'])
    );
    //通过条件数组查询用户
    $info=$this->select($cond);
    if(!empty($info))
        return $info[0];    //返回用户信息
    return array();
}

视图(login.html)

4.4.6 防止SQL注入

通过输入的字符串和SQL语句拼接成具有其他含义的语句,以达到攻击的目的

1561719384318

原理

1561719631643

防范措施:

1、给特殊字符添加转义

2、将单引号替换为空

//单引号添加转义字符
echo addslashes("aa'bb'"),'<br>';	//aa\'bb\'
//字符串替换
echo str_replace("'",'',"aa'bb'");	//aabb

3、md5加密

4、预处理

5、如果确定传递的参数是整数,就需要进行强制类型转换。

1561720464857

4.4.7 防止FQ

FQ:通过直接在地址栏输入URL地址进入模板页面

解决:用户登录成功以后,给用户一个令牌(session),在整个访问的过程中,令牌不消失。

1561721185763

代码实现:

1、登录成功以后,将用户信息保存到会话中

public function loginAction(){
    //第二步:执行登陆逻辑
    if(!empty($_POST)){
        $model=new \Model\UserModel();
        if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
            $_SESSION['user']=$info;    //将用户信息保存到会话中
            $this->success('index.php?p=Admin&c=Admin&a=admin', '登陆成功');
            ....

2、在Controller\Admin目录下创建后台基础控制器(BaseController)

<?php
//后台基础控制器
namespace Controller\Admin;
class BaseController extends \Core\Controller{
    public function __construct() {
        parent::__construct();
        $this->checkLogin();
    }
    //验证是否登录
    private function checkLogin(){
        if(CONTROLLER_NAME=='Login')    //登录控制器不需要验证
            return;
        if(empty($_SESSION['user'])){
            $this->error('index.php?p=Admin&c=Login&a=login', '您没有登录');
        }
    }
}

3、所有的后台控制器都继承后台基础控制器

namespace Controller\Admin;
class AdminController extends BaseController{
    ....
其他控制器也要继承BaseController

4.4.8 更新登陆信息

登陆成功后,更新登陆信息

代码实现:

控制器

public function loginAction(){
    //第二步:执行登陆逻辑
    if(!empty($_POST)){
        $model=new \Model\UserModel();
        if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
            $_SESSION['user']=$info;    //将用户信息保存到会话中
            $model->updateLoginInfo();   //更新登陆信息
            ...

模型(UserModel)

//更新登陆信息
public function updateLoginInfo(){
    //更新的信息
    $_SESSION['user']['user_login_ip']= ip2long($_SERVER['REMOTE_ADDR']);
    $_SESSION['user']['user_login_time']=time();
    $_SESSION['user']['user_login_count']=++$_SESSION['user']['user_login_count'] ;
    //实例化模型
    $model=new \Core\Model('user');
    //更新
    return (bool)$model->update($_SESSION['user']);
}

视图

小结:

1、ip2long:IP转换成整数

2、long2ip:整数转换成IP

3、addslashes:添加转义字符

4、$_SERVER['REMOTE_ADDR']:获取客户端地址

5.2 开启GD扩展

GD库是用来处理图片的。使用GD库,首先在php.ini中开启gd扩展

extension=php_gd2.dll

开启以后就可以使用image开头的函数了。

1561860012762

5.3 创建最简单的图片

步骤

1、创建画布

2、给画布填充颜色(给画布分配的第一个颜色自动填充成背景色)

3、显示图片

<?php
$img=imagecreate(200,100);	//创建图片
//var_dump($img);		//resource(2) of type (gd) 
imagecolorallocate($img,255,0,0);	//给图片分配第一个颜色,默认是背景色
//操作一:显示图片
/*
//告知浏览器用jpg格式显示
header('content-type:image/jpeg');
//显示图片
imagejpeg($img);	//用jpg格式显示图片
*/

//操作二:保存图片(不需要设置header头)
imagejpeg($img,'./tu.jpg');

多学一招

imagepng():将图片输出为png格式
imagegif():将图片输出为gif格式

小结:

1、第一个分配的颜色是背景色

2、要在浏览器显示画布,需要设置header()头

3、保存画布,不需要设置header()头

5.4 填充颜色

给图片分配的第一个颜色自动填充成背景色,如果要更换背景色需要手动的填充颜色。

<?php
$img=imagecreate(200,100);	//创建图片资源
$color=imagecolorallocate($img,200,200,200);
//更改背景色
switch(rand(1,100)%3) {
	case 0:
		$color=imagecolorallocate($img,255,0,0);	//颜色的索引编号
		break;
	case 1:
		$color=imagecolorallocate($img,0,255,0);
		break;
	default:
		$color=imagecolorallocate($img,0,0,255);
}
//填充颜色
imagefill($img,0,0,$color);	
//显示图片
header('content-type:image/png');
imagepng($img);

5.5 验证码

5.5.1 验证码的作用

防止暴力破解

5.5.2 原理

创建一个图片,在图片上写上一串随机字符串

实现步骤:

第一步:生成随机字符串

第二步:创建画布

第三步:将字符串写到画布上

imagestring(图片资源,内置字体,起始点x,起始点y,字符串,颜色编号)

难点:字符串居中

1561865132036

5.5.3 代码实现

<?php
//第一步:创建随机字符串
//1.1  创建字符数组
$all_array=array_merge(range('a','z'),range('A','Z'),range(0,9));	//所有字符数组
$div_array=['1','l','0','o','O','I'];	//去除容易混淆的字符
$array=array_diff($all_array,$div_array);	//剩余的字符数组
unset($all_array,$div_array);		//销毁不需要使用的数组
//1.2	随机获取4个字符
$index=array_rand($array,4);	//随机取4个字符,返回字符下标,按先后顺序排列
shuffle($index);	//打乱字符
//1.3	通过下标拼接字符串
$code='';
foreach($index as $i){
	$code.=$array[$i];
}
//第二步:创建画布
$img=imagecreate(150,30);
imagecolorallocate($img,255,0,0);			//分配背景色
$color=imagecolorallocate($img,255,255,255);	//分配前景色
//第三步:将字符串写到画布上
$font=5;		//内置5号字体
$x=(前端html基础.assetsx($img)-imagefontwidth($font)*strlen($code))/2;
$y=(前端html基础.assetsy($img)-imagefontheight($font))/2;
imagestring($img,$font,$x,$y,$code,$color);
//显示验证码
header('content-type:image/gif');
imagegif($img);

小结

  1. range():生成指定范围的数组
  2. array_merge():合并数组
  3. array_diff():计算数组的差集
  4. array_rand():随机获取数组元素
  5. shuffle():打乱数组
  6. 去除容易混淆的字符
  7. 数组要打乱
  8. 起始点x=(图片宽度-字符串宽度)/2 字符串宽度=字符的宽度*字符的个数
  9. 起始点y=(图片高度-字符高度)/2

运行结果

1561865421771

5.6 打开图片创建验证码

步骤:

1、生成随机字符串

2、打开图片

3、将字符串写到图片上

代码实现

<?php
//第一步:生成随机字符串
$codeSet='2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
$code='';
$max=strlen($codeSet);
for($i=1;$i<=4;$i++){
	$index=rand(0,$max-1);
	$code.=$codeSet[$index];
}
//第二步:打开图片
$path='./captcha/captcha_bg'.rand(1,5).'.jpg';
$img=imagecreatefromjpeg($path);
//第三步:将字符串写到图片上
$font=5;		//内置5号字体
$x=(前端html基础.assetsx($img)-imagefontwidth($font)*strlen($code))/2;
$y=(前端html基础.assetsy($img)-imagefontheight($font))/2;
//随机前景色
$color=imagecolorallocate($img,255,255,255);	//设置背景色
if(rand(1,100)%2)
	$color=imagecolorallocate($img,255,0,0);	//设置背景色	

imagestring($img,$font,$x,$y,$code,$color);
//显示验证码
header('content-type:image/gif');
imagegif($img);

运行结果

1561866597287

多学一招:captcha

1561866719344

5.7 中文验证码

5.7.1 步骤与思考

思考

1、中文验证码需要引入字体文件,内置字体不支持中文

2、使用imagettftext(图片资源,字号大小,角度,起始x坐标,起始y坐标,颜色,字体文件地址,字符串)写入中文

3、字体保存在C:\Windows\Fonts目录下

4、用imagettfbbox()测定中文字符串的宽高,此函数返回8个值,4个角的坐标

1561877629975

步骤

1、生成随机字符串

2、创建画布

3、将字符串写到画布上

5.7.2 代码实现

将黑体拷贝到站点的ttf目录下

1561878246342

代码实现

<?php
//第一步:生成随机字符串
$codeSet='们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来';
$max=mb_strlen($codeSet)-1;	//中文字符的最大索引号
$code='';
for($i=0; $i<4; $i++) {
	$start=rand(0,$max);
	$code.=mb_substr($codeSet,$start,1);
}
//第二步:创建画布
$img=imagecreate(150,40);
imagecolorallocate($img,255,0,0);
//第三步:将字符串写到画布上
//3.1  指定字符串的参数
$color=imagecolorallocate($img,255,255,255);
$size=15;	//字号
$angle=0;	//角度
$fontfile='./ttf/simhei.ttf';	//字体路径
//3.2 测定字符串的范围
$info=imagettfbbox($size,$angle,$fontfile,$code);
$code_w=$info[4]-$info[6];	//字符串的宽度
$code_h=$info[1]-$info[7];	//字符串的高度

$x=(前端html基础.assetsx($img)-$code_w)/2;	//起始点的$x
$y=(前端html基础.assetsy($img)+$code_h)/2;	//起始点的$y
//3.3  将中文字符串写到画布上
imagettftext($img,$size,$angle,$x,$y,$color,$fontfile,$code);	//将文字写到画布上
//显示验证码
header('content-type:image/jpeg');
imagejpeg($img);

小结

1、中文处理需要使用多字节处理

2、使用多字节处理函数需要开启相应的扩展

extension=php_mbstring.dll

3、使用imagettfbbox 测定中文字符串的范围

4、使用imagettftext将中文写到画布上

5.8 水印

1.8.1 文字水印

  1. 在图片上添加文字或图片,目的:宣传,防止盗图
  2. 水印有文字水印和图片水印.
  3. 文字水印实现原理和中文验证码是一样的

步骤

1、打开图片

2、将文字写到图片上

3、输出图片(另存图片)

实现

<?php
//第一步:打开图片
$img=imagecreatefromjpeg('./face.jpg');
//第二步:将文字写到图片上
$color=imagecolorallocate($img,255,0,0);
$size=35;	//字号
$angle=0;	//角度
$fontfile='./ttf/simhei.ttf';	//字体路径
$code='传智播客黑马程序员';

$info=imagettfbbox($size,$angle,$fontfile,$code);
$code_w=$info[4]-$info[6];	//字符串的宽度
$code_h=$info[1]-$info[7];	//字符串的高度

$x=imagesx($img)-$code_w;	//起始点的$x
$y=imagesy($img)-$code_h;	//起始点的$y
//将中文字符串写到画布上
imagettftext($img,$size,$angle,$x,$y,$color,$fontfile,$code);	//将文字写到画布上
//第三步:保存图片
imagejpeg($img,'./face.jpg');

实现效果

1561879059110

5.8.2 图片水印

原理:将水印图片拷贝复制到目标图片上。

步骤:

1、打开源图

2、打开目标图

3、复制源图拷贝到目标图上

实现

<?php
//第一步:打开源图
$src_img=imagecreatefromjpeg('./water.jpg');
//第二步:打开目标图
$dst_img=imagecreatefromjpeg('./face.jpg');
//第三步:将源图复制到目标图上
$dst_x=imagesx($dst_img)-imagesx($src_img);   //开始粘贴的x
$dst_y=imagesy($dst_img)-imagesy($src_img);	  //开始粘贴的y
$src_w=imagesx($src_img);
$src_h=imagesy($src_img);
imagecopy($dst_img,$src_img,$dst_x,$dst_y,0,0,$src_w,$src_h);
//显示水印图
header('content-type:image/jpeg');
imagejpeg($dst_img);

运行结果

1561879908715

5.9 缩略图

上传图片后,将图片变成统一的大小的缩略图。

原理:将源图复制拷贝到目标图上,并缩放大小。

步骤

1、创建目标图

2、打开源图

3、复制源图,拷贝到目标图上

代码实现

<?php
//第一步:创建目标图
$dst_img=imagecreatetruecolor(200,200);
//第二步:打开源图
$src_img=imagecreatefromjpeg('./face.jpg');
//第三步:复制源图拷贝到目标图上,并缩放大小
$src_w=imagesx($src_img);
$src_h=imagesy($src_img);
imagecopyresampled($dst_img,$src_img,0,0,0,0,200,200,$src_w,$src_h);
//第四步:保存缩略图
//header('content-type:image/jpeg');
imagejpeg($dst_img,'./face1.jpg');

注意:imagecreate()imagecreatetruecolor()的区别

imagecreate():创建支持256种颜色的画布
imagecreatetruecolor():创建真彩色画布,支持256*256*256种颜色

5.10 验证码改错

验证码错误不会报具体的错误信息

1561882436203

第一招:注释header

1561882480075

注释掉header后,错误信息就出来了

1561882533981

第二招:如果没有报错,就留心一下图片代码前有无字符串输出,图片前面是不允许有任何字符串输出的

1561882678912

第三招:查看源码,图片代码前是否有空白字符

1561882869099

第四招:如果上面的三招无效,在header()前添加ob_clean();

1561883088298

5.11 在项目中实现验证码

5.11.1 封装验证码类

1、验证码封装Lib类库中

2、在Lib目录下创建Captcha.class.php页面

<?php
namespace Lib;
class Captcha{
    private $width;
    private $height;
    public function __construct($width=80,$height=32) {
        $this->width=$width;
        $this->height=$height;
    }
    //生成随机字符串
    private function generalCode(){
        $all_array=array_merge(range('a','z'),range('A','Z'),range(0,9));	//所有字符数组
        $div_array=['1','l','0','o','O','I'];	//去除容易混淆的字符
        $array=array_diff($all_array,$div_array);	//剩余的字符数组
        unset($all_array,$div_array);		//销毁不需要使用的数组
        $index=array_rand($array,4);	//随机取4个字符,返回字符下标,按先后顺序排列
        shuffle($index);	//打乱字符
        $code='';
        foreach($index as $i)
            $code.=$array[$i];
        $_SESSION['code']=$code;        //保存到会话中
        return $code;
    }
    //创建验证码
    public function entry(){
        $code=$this->generalCode();
        $img=imagecreate($this->width, $this->height);
        imagecolorallocate($img,255,0,0);			//分配背景色
        $color=imagecolorallocate($img,255,255,255);	//分配前景色
        $font=5;		//内置5号字体
        $x=(前端html基础.assetsx($img)-imagefontwidth($font)*strlen($code))/2;
        $y=(前端html基础.assetsy($img)-imagefontheight($font))/2;
        imagestring($img,$font,$x,$y,$code,$color);
        //显示验证码
        header('content-type:image/gif');
        imagegif($img);
    }
    //验证码比较
    public function check($code){
        return strtoupper($code)== strtoupper($_SESSION['code']);
    }
}

5.11.2 使用验证码

1、在控制器中调用验证码类(LoginController)

public function verifyAction(){
    $captcha=new \Lib\Captcha();
    $captcha->entry();
}

2、在视图页面显示验证码

<img src="index.php?p=Admin&c=Login&a=verify" width="80" height="32" class="passcode" onclick='this.src="index.php?p=Admin&c=Login&a=verify&"+Math.random()' />

添加随机数的原因是为了让URL地址变得唯一,防止浏览器缓存。

3、校验输入的验证码

public function loginAction(){
    //第二步:执行登陆逻辑
    if(!empty($_POST)){
        //校验验证码
        $captcha=new \Lib\Captcha();
        if(!$captcha->check($_POST['passcode']))
            $this->error ('index.php?p=Admin&c=Login&a=login', '验证码错误');
        ...

运行结果

1561885369857

5.12 使用的函数

imagecreate():创建画布
imagecreatetruecolor():创建支持真彩色的画布
imagecolorallocate():给画布分配颜色
imagejpeg():将图片以jpeg格式输出
imagegif():将图片以gif格式输出
imagepng():将图片以png格式输出
imagefill():填充颜色
imagesx():获取图片宽度
imagesy():获取图片高度
imagefontwidth():获取内置字体宽度
imagefontheight():获取内置字体高度
imagestring():将字符串写到图片上
imagecreatefromjpeg():打开jpg创建图片资源
imagecreatefrompng():打开png创建图片资源
imagecreatefromgif():打开gif创建图片资源
imagettfbbox():测定中文字体的范围
imagettftext():将中文字体写到图片上
imagecopy():图片拷贝
imagedestroy():销毁图片资源
imagecopyresampled():拷贝图片并缩放大小

6.2 文件上传

6.2.1 封装文件上传类

1、在Lib目录下创建Upload.class.php

<?php
namespace Lib;
class Upload{
    private $path;  //上传的路径
    private $size;  //上传的大小
    private $type;  //允许上传的类型
    private $error; //保存错误信息
    
    public function __construct($path,$size,$type) {
        $this->path=$path;
        $this->size=$size;
        $this->type=$type;
    }
    //返回错误信息
    public function getError(){
        return $this->error;
    }
    /*
     * 文件上传
     * @param $files array $_FILES[]
     * @return bool|string 成功返回文件路径,失败返回false
     */
    public function uploadOne($files){
        if($this->checkError($files)){  //没有错误就上传
            $foldername=date('Y-m-d');		//文件夹名称
            $folderpath= $this->path.$foldername;	//文件夹路径
            if(!is_dir($folderpath))
                mkdir($folderpath);
            $filename=uniqid('',true).strrchr($files['name'],'.');//文件名
            $filepath="$folderpath/$filename";	//文件路径
            if(move_uploaded_file($files['tmp_name'],$filepath))
                return "{$foldername}/{$filename}";
            else{
                $this->error='上传失败<br>';
                return false;
            }
        }
        return false;
    }
    //验证上传是否有误
    private function checkError($files){
        //1、验证错误号
        if($files['error']!=0){
            switch($files['error']) {
                case 1:
                    $this->error='文件大小超过了php.ini中允许的最大值,最大值是:'.ini_get('upload_max_filesize');
                    return false;
                case 2:
                    $this->error='文件大小超过了表单允许的最大值';
                    return false;
                case 3:
                    $this->error='只有部分文件上传';
                    return false;
                case 4:
                    $this->error='没有文件上传';
                    return false;
                case 6:
                    $this->error='找不到临时文件';
                    return false;
                case 7:
                   $this->error='文件写入失败';
                    return false;
                default:
                   $this->error= '未知错误';
                    return false;
            }
        }
        //2、验证格式
	$info=finfo_open(FILEINFO_MIME_TYPE);
	$mime=finfo_file($info,$files['tmp_name']);
	if(!in_array($mime, $this->type)){
            $this->error='只能上传'.implode(',', $this->type).'格式';
            return false;
	}
	//3、验证大小
	if($files['size']> $this->size){
            $this->error='文件大小不能超过'.number_format($this->size/1024,1).'K';
            return false;
	}
	//4、验证是否是http上传
	if(!is_uploaded_file($files['tmp_name'])){
            $this->error='文件不是HTTP POST上传的<br>';
            return false;
        }
        return true;
    }
}

6.2.2 封装缩略图类

在Lib目录下创建Image.class.php

<?php
namespace Lib;
class Image{
    /*
     * 制作缩略图
     * @param $src_path 源图的路径
     */
    public function thumb($src_path,$prefix='small_',$w=200,$h=200){
        $dst_img=imagecreatetruecolor($w,$h);   //目标图
        $src_img=imagecreatefromjpeg($src_path);    //源图
        $src_w=imagesx($src_img);
        $src_h=imagesy($src_img);
        imagecopyresampled($dst_img,$src_img,0,0,0,0,$w,$h,$src_w,$src_h);
        $filename=basename($src_path);  //文件名
        $foldername=substr(dirname($src_path),-10); //目录名
        $save_path= dirname($src_path).'/'.$prefix.$filename;
        imagejpeg($dst_img,$save_path);
        return "{$foldername}/{$prefix}{$filename}";
    }
}

6.2.3 实现文件上传

1、register.html

 <form action="" method="post" enctype="multipart/form-data">
     ...

2、更改注册控制器

public function registerAction(){
    //第二步:执行注册逻辑
    if(!empty($_POST)){
        //文件上传
        $path=$GLOBALS['config']['app']['path'];
        $size=$GLOBALS['config']['app']['size'];
        $type=$GLOBALS['config']['app']['type'];
        $upload=new \Lib\Upload($path, $size, $type);
        if($filepath=$upload->uploadOne($_FILES['face'])){
            //生成缩略图
            $image=new \Lib\Image();
            $data['user_face']=$image->thumb($path.$filepath,'s1_');
        }else{
            $this->error('index.php?p=Admin&c=Login&a=register', $upload->getError());
        }
        //文件上传结束
        ...

3、配置文件

 'app'       =>array(
        'path'  =>  './Public/Uploads/',
        'size'  =>  1234567,
        'type'  =>  ['image/png','image/jpeg','image/gif'],

6.3 登录模块

6.3.1 记住密码

登录成功后,如果需要记录用户名和密码,则将用户名和密码记录在cookie中

1561951991167

打开登录页面的时候,获取cookie的值

1561952025351

在视图页面(login.html)页面显示cookie的信息

<input type="text" class="input" name="username" placeholder="登录账号" value="<?=$name?>"  />
...
<input type="password" class="input" name="password" placeholder="登录密码" value="<?=$pwd?>" />

运行结果

1561952112284

6.3.2 安全退出

退出:退出的时候不销毁令牌
安全退出:退出的时候销毁了令牌

top.html

<a class="button button-little bg-yellow" href="index.php?p=Admin&c=Login&a=logout" target="_top">安全退出</a>

_top:表示在最顶端的窗口中打开

控制器(LoginController)

public function logoutAction(){
    session_destroy();
    header('location:index.php?p=Admin&c=Login&a=login');
}

6.4 Smarty简介

6.4.1 Smarty的引入

1、为了分工合作,模板页面中最好不要出现PHP的代码。

2、需要将表现和内容相分离

6.2.2 Smarty介绍

1561953188908

6.5 自定义Smarty

6.3.1 演化一:(smarty生成混编文件)

在模板中不能出现PHP定界符,标准写法如下:

1、html代码

<body>
{$title}
</body>

2、PHP代码

<?php
	$title='锄禾';
	require './1-demo.html';

运行结果

1561962161195

不能解析的原因是:PHP不能识别 { 和 }

解决:

将大括号替换成PHP的定界符

1561962261095

代码实现

<?php
$title='锄禾';
$str=file_get_contents('./index.html');
$str=str_replace('{','<?php echo ',$str);	//替换左大括号
$str=str_replace('}',';?>',$str);			//替换右大括号
file_put_contents('./index.html.php', $str);	//写入混编文件
require './index.html.php';	//包含混编文件

运行

1561962843715

6.3.2 演化二:(smarty封装)

由于每个页面都要替换定界符,所以需要将替换定界符的代码封装起来

由于封装在类中,所有访问的方法需要通过面向对象的方式来访问

1561963657349

1、创建Smarty.class.php

<?php
class Smarty{
	private $tpl_var=array();
	//赋值
	public function assign($k,$v){
		$this->tpl_var[$k]=$v;
	}
	/*
	*作用:编译模板
	*@param $tpl string 模板的路径
	*/
	public function compile($tpl){
		$com_file=$tpl.'.php';		//混编文件地址
		$str=file_get_contents($tpl);
		$str=str_replace('{$','<?php echo $this->tpl_var[\'',$str);	//替换左大括号
		$str=str_replace('}','\'];?>',$str);			//替换右大括号
		file_put_contents($com_file, $str);	//写入混编文件
		require $com_file;	//包含混编文件
	}
}

2、在index.php

<?php
require './Smarty.class.php';
$smarty=new Smarty();
$smarty->assign('title','锄禾');
$smarty->compile('./index.html');

小结:

1、需要将外部的变量赋值到对象的内部

2、要通过面向对象的方式访问

6.3.3 演化三:(有条件的生成混编文件)

混编文件存在并且是最新的就直接包含,否则就重新生成

模板文件修改时间<混编文件修改时间 => 混编文件是最新的

Smarty类中的代码编译代码如下

<?php
class Smarty{
	private $tpl_var=array();
	//赋值
	public function assign($k,$v){
		$this->tpl_var[$k]=$v;
	}
	/*
	*作用:编译模板
	*@param $tpl string 模板的路径
	*/
	public function compile($tpl){
		$com_file=$tpl.'.php';		//混编文件地址
		//文件存在,并且模板文件修改时间<混编文件修改时间
		if(file_exists($com_file) && filemtime($tpl)<filemtime($com_file))
			require $com_file;
		else{
			$str=file_get_contents($tpl);
			$str=str_replace('{$','<?php echo $this->tpl_var[\'',$str);	//替换左大括号
			$str=str_replace('}','\'];?>',$str);			//替换右大括号
			file_put_contents($com_file, $str);	//写入混编文件
			require $com_file;	//包含混编文件
		}
	}
}

小结:

生成混编文件的条件

1、混编不存在

2、模板修改了, 模板文件修改时间>混编文件修改时间,说明模板修改过了。

6.3.4 演化四:文件分类存放

  1. 模板文件:view

  2. 混编文件:viewc

  3. Smarty文件:smarty.class.php

Smarty.class.php代码如下:

<?php
class Smarty{
   public $template_dir='./templates/';	//默认模板目录
   public $templatec_dir='./templates_c/';	//默认混编目录

   private $tpl_var=array();
   //赋值
   public function assign($k,$v){
   	$this->tpl_var[$k]=$v;
   }
   /*
   *作用:编译模板
   *@param $tpl string 模板的名字
   */
   public function compile($tpl){
   	$tpl_file=$this->template_dir.$tpl;	//拼接模板地址
   	$com_file=$this->templatec_dir.$tpl.'.php';		//混编文件地址
   	//文件存在,并且模板文件修改时间<混编文件修改时间
   	if(file_exists($com_file) && filemtime($tpl_file)<filemtime($com_file))
   		require $com_file;
   	else{
   		$str=file_get_contents($tpl_file);
   		$str=str_replace('{$','<?php echo $this->tpl_var[\'',$str);	//替换左大括号
   		$str=str_replace('}','\'];?>',$str);			//替换右大括号
   		file_put_contents($com_file, $str);	//写入混编文件
   		require $com_file;	//包含混编文件
   	}
   }
}

index.php代码如下

<?php
require './Smarty/Smarty.class.php';
$smarty=new Smarty();
$smarty->template_dir='./view/';	//更改模板目录
$smarty->templatec_dir='./viewc/';	//更改混编目录
$smarty->assign('title','锄禾');
$smarty->compile('index.html');

6.3.5 演化五:封装编译方法

编译的方法是smarty的核心方法,核心方法一般是不可以直接调用,需要进行二次封装

smarty.class.php

<?php
class Smarty{
	...
	public function display($tpl){
		require $this->compile($tpl);
	}
	/*
	*作用:编译模板
	*@param $tpl string 模板的名字
	*/
	private function compile($tpl){
		..
		//文件存在,并且模板文件修改时间<混编文件修改时间
		if(file_exists($com_file) && filemtime($tpl_file)<filemtime($com_file))
			return $com_file;    //返回混编地址
		else{
			$str=file_get_contents($tpl_file);
			$str=str_replace('{$','<?php echo $this->tpl_var[\'',$str);	//替换左大括号
			$str=str_replace('}','\'];?>',$str);			//替换右大括号
			file_put_contents($com_file, $str);	//写入混编文件
			return $com_file;	//返回混编地址
		}
	}
}

index.php

<?php
...
$smarty->assign('title','锄禾');
$smarty->display('index.html');	//传递文件名

6.6 官方Smarty介绍

6.6.1 smarty目录结构

www.smarty.net网站下载最新的smarty版本

1561967552187

解压

1561967604487

libs目录结构

1561967720685

需要掌握的smarty的属性和方法

public $left_delimiter = "{";		//左界定
public $right_delimiter = "}";		//右界定
protected $template_dir = array('./templates/');	//默认模板目录
protected $compile_dir = './templates_c/';			//默认混编目录
protected $config_dir = array('./configs/');		//默认配置目录
protected $cache_dir = './cache/';					//默认缓存目录


public function setTemplateDir(){}					//设置模板文件夹
public function setConfigDir(){}					//设置配置文件夹
public function setCompileDir(){}					//设置混编文件夹
public function setCacheDir(){}						//设置缓存文件夹

练习:

以下关于Smarty配置描述正确的是(ABCD)
	A: 使用left_delimiter属性可以修改Smarty左定界符;	 
	B: 使用right_delimiter属性可以修改Smarty右定界符;	 
	C: 使用setTemplateDir()方法可以重新指定默认模板工作目录;	 
	D: 使用setCompileDir()方法可以重新指定默认编译文件工作目录。	 

6.6.2 smarty简单的操作

1、将libs目录拷贝到站点下,改名为smarty

2、创建模板目录templates

3、创建混编目录templates_c

4、在站点下创建1-demo.php

<?php
require './Smarty/Smarty.class.php';
$smarty=new Smarty();
$smarty->assign('title','锄禾');
$smarty->left_delimiter='{{';		//更改左界定
$smarty->right_delimiter='}}';		//更改右界定
$smarty->setTemplateDir('./view/');	//设置模板目录
$smarty->setCompileDir('./viewc/');	//设置混编目录
$smarty->display('1-demo.html');

在templates下创建demo1.html

<body>
	{{$title}}
</body>

6.6.3 注释

语法:{* *}

1561969175401

注意:smarty注释在源码中看不见。

思考:已知smarty的定界符是{* 和 *},那么它的注释是什么?

答:{** **}

7.2 变量

smarty中变量有3中,普通变量、配置变量、保留变量

1、普通变量

普通变量就是我们自己定义的变量

方法一:在PHP中定义

$smarty->assign('name','tom');

方法二:可以在模板定义

语法:{assign var='变量名' value='值'}

例如:{assign var='sex' value='男'}

简化写法:

{$sex='男'}

例题:

php代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->assign('name','tom');		//给变量赋值
$smarty->display('1-demo.html');

HTML代码

<body>
	姓名:{$name}  <br>

	{assign var='age' value=20}
	年龄:{$age}<br>

	{$add='北京'}
	地址:{$add}
</body>

运行结果

1562117254254

2、保留变量

Smarty中有一个特殊的保留变量(内置变量),类似于PHP中的所有的超全局变量、常量、时间等信息

表达式 描述
获取get提交的name的值
获取post提交的name的值
获取get和post提交的name的值
获取cookie中的name的值
获取session中的name的值
获取常量name
获取服务器的虚拟目录地址
获取配置文件中的值
时间戳
获取左界定
获取右界定

例题

PHP代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
define('name', '常量name');
setcookie('name','cookie的值');
$_SESSION['name']='session的值';
$smarty->display('1-demo.html');

HTML代码

<body>
get提交:{$smarty.get.name}<br>
post提交:{$smarty.post.name}<br>
request提交:{$smarty.request.name}<br>
常量:{$smarty.const.name}<br>
cookie的值:{$smarty.cookies.name}<br>
session:{$smarty.session.name}<br>
时间戳:{$smarty.now}<br>
版本号:{$smarty.version}<br>
根目录:{$smarty.server.DOCUMENT_ROOT}<br>
左界定:{$smarty.ldelim}<br>
右界定:{$smarty.rdelim}
</body>

运行结果

1562117928996

3、配置变量

从配置文件中获取变量值,配置文件默认的文件夹是configs

1、在站点下创建配置文件夹configs

2、在configs目录下创建smarty.conf文件

color='#FF0000';
size='15px';

3、PHP页面

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->display('1-demo.html');

4、HTML页面

{config_load file='smarty.conf'}   <!--引入配置文件-->
<style>
body{
	color:{#color#};
	font-size: {$smarty.config.size}
}
</style>

小结:

1、要使用配置文件中的值,首先必须引入配置文件,通过{config_load}标签引入

2、获取配置文件中的值的方法有两种

​ 第一:{#变量名#}

​ 第二:{$smarty.config.变量名}

多学一招:配置文件中的节

在配置文件中,‘[ ]’表示配置文件的段落

例题:

配置文件

color=#FF0000
size=30px
    
[spring]	# 配置文件中的段落
color=#009900;
size=20px;

[winter]
color=#000000;
size=5px;

注意:

1、全局的一定要写在节的前面

2、配置文件中[]表示节

3、配置文件中的注释是 #

HTML页面

{config_load file='smarty.conf' section='winter'}   -- 通过section引入配置文件中的段落
<style>
body{
	color:{#color#};
	font-size: {$smarty.config.size}
}
</style>

7.3 运算符

smary中的运算符是PHP是一样的。除此以外,smarty还支持如下的运算符。

运算符 描述
eq equal 相等
neq not equal 不等于
gt greater than 大于
lt less than 小于
lte less than or equal 小于等于
gte great than or equal 大于等于
is even 是偶数
is odd 是奇数
is not even 不是偶数
is not odd 不是奇数
not
mod 求模取余
div by 被整除
is [not] div by 能否被某数整除,例如:{if $smarty.get.age is div by 3}...
is [not] even by 商的结果是否为偶数
is [not] odd by 商的结果是否为奇数

7.4 判断

语法:

{if 条件}

{elseif 条件}

{else}

{/if}

例题:

php代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->display('1-demo.html');

html代码

<body>
{if is_numeric($smarty.get.score)}    {#判断是否是数字#}
	{if $smarty.get.score gte 90}
		A
	{elseif $smarty.get.score gte 80}
		B
	{else}
		C
	{/if}
{else}
	不是数字
{/if}

<hr>
{if $smarty.get.score is even}
	是偶数
{elseif $smarty.get.score is odd}
	是奇数
{/if}
</body>

运行结果

1562121483179

小结:在判断中是可以使用PHP的函数的

7.5 数组

smarty中访问数组的方式有两种

数组[下标]
数组.下标

PHP代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$stu=array('tom','berry');		//索引数组
$emp=array('name'=>'rose','sex'=>'女');	//关联数组
$goods=array(
	array('name'=>'手机','price'=>22),
	array('name'=>'钢笔','price'=>10)
);
$smarty->assign('stu',$stu);
$smarty->assign('emp',$emp);
$smarty->assign('goods',$goods);
$smarty->display('2-demo.html');

HTML代码

<body>
学生:{$stu[0]}-{$stu.1}  <br>
雇员:{$emp['name']}-{$emp.sex}<br>
商品:
<ul>
	<li>{$goods[0]['name']}</li>
	<li>{$goods[0].price}</li>
	<li>{$goods.1['name']}</li>
	<li>{$goods.1.price}</li>
</ul>
</body>

运行结果

1562121978495

7.6 循环

smarty中支持的循环有:{for}、{while}、{foreach}、{section}。对于开发来说用的最多就是{foreach}循环

7.6.1 for

语法:

{for 初始值 to 结束值 [step 步长]}

{/for}
 默认步长是1

例题

<body>
{for $i=1 to 5}
	{$i}:锄禾日当午<br>
{/for}
<hr>
{for $i=1 to 5 step 2}
	{$i}:锄禾日当午<br>
{/for}
</body>

运行结果

1562122930604

7.6.2 while

语法

{while 条件}

{/while}

例题(输出5句):

<body>
{$i=1}
{while $i<=5}
	{$i++}:锄禾日当午<br>
{/while}
</body>

7.6.3 foreach

既能遍历关联数组也能遍历索引数组

语法:

{foreach 数组 as $k=>$v}

{foreachelse}
	没有数组输出
{/foreach}

foreach的属性

@index:从0开始的索引
@iteration:从1开始的编号
@first:是否是第一个元素
@last:是否是最后一个元素

PHP代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->assign('stu',array('first'=>'tom','second'=>'berry','third'=>'ketty','forth'=>'rose'));
$smarty->display('3-demo.html');

html代码

<table border='1' bordercolor='#000' width='780'>
	<tr>
		<th>是否是第一个元素</th>
		<th>索引</th>
		<th>编号</th>
		<th>键</th>
		<th>值</th>
		<th>是否是最后一个元素</th>
	</tr>
	{foreach $stu as $k=>$v}
	<tr>
		<td>{$v@first}</td>
		<td>{$v@index}</td>
		<td>{$v@iteration}</td>
		<td>{$k}</td>
		<td>{$v}</td>
		<td>{$v@last}</td>
	</tr>
	{foreachelse}
		没有输出
	{/foreach}
</table>

运行结果

1562124457735

7.6.4 section

section不支持关联数组,只能遍历索引数组

语法:

{section name=自定义名字 loop=数组}
{/section}

例题:

php

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->assign('stu',array('tom','berry'));
$smarty->display('4-demo.html');

html代码

<table border='1' bordercolor='#000' width='780'>
<tr>
	<th>是否是第一个元素</th>
	<th>索引</th>
	<th>编号</th>
	<th>值</th>
	<th>是否是最后一个元素</th>
</tr>
{section name=s loop=$stu}
<tr>
	<td>{$smarty.section.s.first}</td>
	<td>{$smarty.section.s.index}</td>
	<td>{$smarty.section.s.iteration}</td>
	<td>{$stu[s]}</td>
	<td>{$smarty.section.s.last}</td>
</tr>
{sectionelse}
	没有输出
{/section}
</table>

1562125165784

7.7 函数

函数有两种,自定义函数和内置函数

smarty的内置函数就是封装的PHP的关键字

1562125393895

7.8 变量修饰器

7.8.1 变量修饰器

变量修饰器的本质就是PHP函数,用来转换数据

php代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->display('5-demo.html');

html代码

<body>
转成大写:{'abc'|upper} <br>
转成小写:{'ABC'|lower} <br>
默认值:{$add|default:'地址不详'}<br>
去除标签:{'<b>你好吗</b>'|strip_tags}<br>
实体转换:{'<b>你好吗</b>'|escape}<br>
日期:{$smarty.now|date_format:'%Y-%m-%d %H:%M:%S'}
多个管道连续使用:{'<b>boy</b>'|strip_tags|upper}<br>
</body>

运行结果

1562126429832

注意:

1、将PHP的关键字或函数封装成标签称为函数,将PHP关键字封装成smarty关键字称为修饰器。内部的本质都是、PHP函数或PHP关键字。

2、|称为管道运算符,将前面的参数传递后后面的修饰器使用

7.8.2 自定义变量修饰器

变量修饰器存放在plugins目录中

规则:

  1. 文件的命名规则:modifier.变量修饰器名称.php
  2. 文件内方法命名规则:smarty_modifier_变量修饰器名称(形参...){}

例题

1、在plugins目录中创建modifier.cal.php页面

<?php
function smarty_modifier_cal($num1,$num2,$num3){
	return $num1+$num2+$num3;
}

2、在模板中调用

{10|cal:20:30}

10作为第一个参数传递
参数之间用冒号分隔

7.9 避免Smarty解析

smarty的定界符和css、js中的大括号产生冲突的时候,css、js中的大括号不要被smarty解析

方法一:更换定界符

方法二:左大括号后面添加空白字符

方法三:{literal} {/literal}

smarty不解析{literal} {/literal}中的内容

<style>
{literal}
body{color: #FF0000;}
{/literal}
</style>

7.10 缓存

缓存:页面缓存、空间缓存、数据缓存。smarty中的缓存就是页面缓存

smarty的缓存是页面缓存。

7.10. 1 开启缓存

$smarty->caching=true|1;		//开启缓存

7.10.2 缓存的更新

方法一:删除缓存,系统会重新生成新的缓存文件

方法二:更新了模板文件,配置文件,缓存自动更新

方法三:过了缓存的生命周期,默认是3600秒

方法四:强制更新

PHP代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->caching=true;		//开启缓存
if(date('H')>=9)
	$smarty->force_cache=true;	//强制更新缓存
$smarty->display('6-demo.html');

7.10.3 缓存的生命周期

$smarty->cache_lifetime=-1 | 0 | N
-1:永远不过期
0:立即过期
N:有效期是N秒,默认是3600秒

PHP代码

$smarty->cache_lifetime=3;	//缓存的生命周期

7.10.4 局部不缓存

局部不缓存有两种方法

1、变量不缓存    {$变量名  nocache}
2、整个块不缓存   {nocache}   {/nocache}

代码

不缓存:{$smarty.now nocache}  <br>
不缓存:{nocache}
	{$smarty.now}<br>
{/nocache}

7.10.5 缓存分页

通过$smarty->display(模板,识别id)。通过识别id来缓存分页、集合

PHP页面

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->caching=1;
$smarty->display('7-demo.html',$_GET['pageno']);

html页面

<body>
	这是第{$smarty.get.pageno}页
</body>

运行结果

1562137760608

7.10.6 缓存集合

1555322656802

每个组合都会产生缓存

PHP代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
$smarty->caching=1;
$color=$_GET['color'];
$size=$_GET['size'];
$smarty->display('7-demo.html',"$color|$size");

HTML代码

<body>
	颜色:{$smarty.get.color}<br>
	大小:{$smarty.get.size}
</body>

运行结果

1562138211626

1562138229994

7.10.7 清除缓存

$smarty->clearCache(模板,[识别id])
$smarty->clearAllCache();   //清除所有缓存

代码

<?php
require './Smarty/smarty.class.php';
$smarty=new Smarty();
//$smarty->clearCache('7-demo.html',1);
//$smarty->clearCache('7-demo.html','red|10');
//$smarty->clearCache('7-demo.html');
$smarty->clearAllCache();	//清除所有缓存

7.11 将smarty集成到项目中

1、将smarty拷贝到Lib目录下

1562139749261

2、实现smarty类的自动加载

 private static function initAutoLoad(){
        spl_autoload_register(function($class_name){
            //Smarty类存储不规则,所以将类名和地址做一个映射
            $map=array(
                'Smarty'    =>  LIB_PATH.'Smarty'.DS.'Smarty.class.php'
            );
            
           ...
            elseif(isset($map[$class_name]))
                $path=$map[$class_name];
            else   //控制器
                $path=/index.php?s=-news.$class_name.'.class.php'; 
            if(file_exists($path) && is_file($path))
                require $path;
        });
    }

3、创建混编目录,并且定义混编目录地址

1562140844265

private static function initRoutes(){
  ...
    define('__VIEW__',VIEW_PATH.$p.DS);     //当前视图的目录地址
    define('__VIEWC__', APP_PATH.'Viewc'.DS.$p.DS); //混编目录
}

4、由于前后台都要启动模板,所以应该在基础控制器中实例化smarty

<?php
//基础控制器
namespace Core;
class Controller{
    protected $smarty;
    use \Traits\Jump;
    
    public function __construct() {
        $this->initSession();
        $this->initSmarty();
    }
    //初始化session
    private function initSession(){
        new \Lib\Session();
    }
    //初始化Smarty
    private function initSmarty(){
        $this->smarty=new \Smarty();
        $this->smarty->setTemplateDir(__VIEW__);   //设置模板目录
        $this->smarty->setCompileDir(__VIEWC__);	//设置混编目录
    }
}

5、在控制器中使用smarty

class ProductsController extends BaseController{
    //获取商品列表
    public function listAction() {
        //实例化模型
        $model=new \Model\ProductsModel();
        $list=$model->select();
        //加载视图
        //require __VIEW__.'products_list.html';
        $this->smarty->assign('list',$list);
        $this->smarty->display('products_list.html');
    }

6、在模板中更改如下:

{foreach $list as $rows}
<tr>
        <td>{$rows['proID']}</td>
        <td>{$rows['proname']}</td>
        <td>{$rows['proprice']}</td>
        <td><a href="index.php?p=Admin&c=Products&a=edit&proid={$rows['proID']}">修改</a></td>
        <td><a href="javascript:void(0)" onclick="if(confirm('确定要删除吗')){ location.href='index.php?p=Admin&c=Products&a=del&proid={$rows['proID']}'}">删除</a></td>
</tr>
{/foreach}