开发工具 / 编程开发 / 软件架构 · 2022年4月13日 0

PHP学习之Web框架基础学习笔记

导语:网络上有很多php的Web开发框架,thinkphp、laravel、yii、symfony、phalcon等等。本文不做任何框架之间优劣高低的比较和判断。主要是根据已有的项目经验,对thinkphp、laravel、yii这三个框架的基本目录层次、数据库与ORM、路由与控制器、视图、框架的事件及第三方接口交互等方面做简单的摘抄笔记。

参考文档:

百度百科-框架: https://baike.baidu.com/item/%E6%A1%86%E6%9E%B6/1212667#4_3 

thinkphp : http://www.thinkphp.cn/   

laravel : https://laravel.com/ 

yii : https://www.yiichina.com/ 

1、框架及其存在的意义

在软件工程领域,框架和设计模式这两个概念可以拿来进行对比,因为具有相似点。构件通常是代码重用,而设计模式是设计重用,框架则介于两者之间,部分代码重用,部分设计重用,有时分析也可重用。

1.1、什么是框架

框架(framework)是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。

框架( Framework )是构成一类特定软件可复用设计的一组相互协作的类。框架规定了你的应用的体系结构。它定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力于应用本身的特定细节

1.2、框架存在的意义

因为软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事务处理,安全性,数据流控制等问题。还有框架一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。

软件为什么要分层? 为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源等等。

框架的意义是对于人而言的。是为了让人理解代码、复用代码而创造的。对于机器而言,再好再优化的框架,都是拖累。对比一篇文章: 【web开发基础-怎样快速编写一个粗糙的文章管理】里的原生php代码,仅需要一个php文件就可以实现数据库连接、数据展示了。若使用框架,则会预加载各种类和文件。

至于php的web框架用哪一个,网上众说纷纭,thinkphp入门简单、symfony门槛高等等。还引起了各种框架的性能对比。但实际情况是项目用哪种框架,开发人员就得用哪种框架。当项目是新项目,而且自己还能决定技术选型。如果是要用的且没有学过的框架,花一天时间通看一遍,然后操作一遍“快速入门”(这个章节每个成熟的框架都是有的),一般而言就可以开始项目之旅了。遇到什么问题,像查工具书一样,去看看官网文档。

2、安装与目录结构对比

Composer 是 PHP5.3以上 的一个依赖管理工具。它允许你声明项目所依赖的代码库,它会在你的项目中为你安装他们。

composer中文网: https://www.phpcomposer.com/ 

2.1、thinkphp的安装

截至目前tp已经更新到tp6了,线上很多运行的项目都还是tp3.2和tp5的。所以主要讲tp3.2和tp5。

严格来说,ThinkPHP无需安装过程,这里所说的安装其实就是把ThinkPHP框架放入WEB运行环境。直接从官网下载zip包解压 或者git clone 代码。

thinkphp 5 也可以通过composer 安装,命令行切换到web根目录并执行:

# composer create-project topthink/think=5.0.* tp5 –prefer-dist 

项目的目录结构:

【thinkphp 3.2】

【thinkphp 5】

2.2、laravel的安装

截至目前laravel 已经更新到 laravel 7.x 版本,线上项目大多运行的是laravel 5。所以主要以laravel 5.8 版本 为准。

laravel 对系统有一些要求,这个可以查看官网详细信息。laravel 使用composer来管理项目依赖,因此在使用laravel之前,必须先安装好composer。

在终端通过composer创建项目,输入命令行:

#composer create-project –prefer-dist laravel/laravel 项目名称 laravel版本号

例如: #composer create-project –prefer-dist laravel/laravel blog “5.8.*”

项目的目录结构:

2.3、yii的安装

截至目前Yii已经出到2.0了,说是Yii 1.0 和Yii 2.0的版本差别比较大,类似于python 2.x和python 3.x 的区别。因为仅仅是作为学习笔记,各个版本之间的差异对学习而言并不重要,所以下面内容主要以 Yii 2.0 为准。

Yii的安装方式有很多,建议使用composer【一招鲜吃遍天】

在终端通过composer创建项目,输入命令行:

#composer create-project –prefer-dist yiisoft/yii2-app-basic 项目名称

项目的目录结构:

上图所示是yii生成的应用中最重要的目录和文件【来源官网】真实的目录结构会多出很多东西,比如tests、mail、widgets目录等,有兴趣可自行到官网查阅具体的用法和含义。

2.4、小结-目录分析

除了tp3.2,目前主流的php框架都是支持composer安装和项目管理的。因此大致了解并使用composer,对一个phper来说是必要的。tp3.2|tp5、laravel和yii 安装好后,看起来目录结构差异有点大,如果稍微比较分析一下,可以发现它们的内涵大同小异。

1、应用入口文件index.php 。【名字不一定是index.php,也不一定只有一个入口,比如: pc.php,mobile.php,admin.php】

2、应用配置config。【一般配置数据库连接、第三方平台配置、web应用配置等】

3、模型目录models。【比较特殊的是laravel,是没有默认的models文件夹,需要用户自定义】

4、控制器目录 controller。

5、视图目录views。

6、资源目录 public|resource|assets【叫法不一,这个其实并非必要的,有些项目的资源是存储在资源服务器上的】

7、运行目录 runtime。

综上,就目录结构而言,tp3.2 其实是最简洁实用的,核心目录功能,该有的都有了。如果有兴趣,也可以参考参考tp3.2的源码,然后搭建自己的MVC框架。

3、数据访问层及ORM

3.1、thinkphp

【注意thinkphp 3.2和thinkphp 5 模型的CURD 的写法差别有点大,因此要分开比较】

1、执行mysql语句

thinkphp3.2

$Model = new \Think\Model() // 实例化一个model对象 没有对应任何数据表
#query方法用于执行SQL查询操作,如果数据非法或者查询错误则返回false,否则返回查询结果数据集(同select方法)
$Model->query($sql语句);
#execute用于更新和写入数据的sql操作,如果数据非法或者查询错误则返回false ,否则返回影响的记录数。
$Model->execute($sql语句);

thinkphp5
Db::query($sql语句);	//query方法执行sql查询操作
Db::execute($sql语句);	//execute方法执行更新和写入

2、查询数据

thinkphp 3.2

$model = M("User"); // 实例化User对象
$condition[“status”] = 1;		//查询条件
#读取单行数据
$data = $User->where($condition)->find();
#读取数据集
$list = $User->where($condition)->order('create_time')->limit(10)->select();
#读取字段值
$nickname = $User->where('id=3')->getField('nickname');
#读取某一列的字段值
$User->getField('id',true); // 获取id数组

thinkphp 5

#查询单行数据
// table方法必须指定完整的数据表名
Db::table('user')->where('id',1)->find();
#查询数据集
Db::table(‘user’)->where('status',1)->select();
#查询某个字段值
Db::table('user')->where('id',1)->value('name');
#查询某一列的值
Db::table('user')->where('status',1)->column('name');

3、添加数据

thinkphp3.2

$User = M("User"); // 实例化User对象 也可以用 D(“User”);或者 M()->table(‘user’);
$data = ['foo' => 'bar', 'bar' => 'foo'];
$User->add($data); //或者 $User->data($data)->add();
$User->addAll($dataList); //数据的批量写入,【该功能需要3.2.3以上版本,3.2.3以下版本仅对mysql数据库支持】

thinkphp 5

Db::table('think_user')->insert($data); 
Db::name('user')->insertGetId($data);		//【有数据库前缀】新增数据并返回主键值
Db::name('user')->insertAll($dataList);

#模型
$user = new User;
$user->data([
    'name' => 'thinkphp',
    'email' => 'thinkphp@qq.com'
]);
$user->save();
$user->saveAll($list);		//多条数据

4、更新数据

thinkphp3.2

#更新数据表中的数据
$User = M("User"); // 实例化User对象
// 要修改的数据对象属性赋值
$data = ['foo' => 'bar', 'bar' => 'foo'];
$User->where('id=5')->save($data); // 根据条件更新记录

#更新某个字段的值
$User-> where('id=5')->setField('name',”new_name”);
#对于统计字段(通常指的是数字类型)的更新,系统还提供了setInc和setDec方法
$User->where('id=5')->setInc('score',3); // 用户的积分加3【不填数字3则默认为1】
$User->where('id=5')->setDec('score',5); // 用户的积分减5【不填数字5则默认为1】

thinkphp5

#更新数据表中的数据
Db::table('think_user')->where('id', 1)->update(['name' => 'thinkphp']);

#更新某个字段的值
Db::table('think_user')->where('id',1)->setField('name', 'thinkphp');
#自增或自减
Db::table('think_user')->where('id', 1)->setInc('score', 5);
Db::table('think_user')->where('id', 1)->setDec('score', 5);

#模型更新
$user = User::get(1);
$user->name    = 'thinkphp';
$user->email   = 'thinkphp@qq.com';
$user->save();
User::where('id', 1)->update(['name' => 'thinkphp']);	//静态方法

5、删除数据

thinkphp3.2

$User = M("User"); // 实例化User对象
$User->where('status=0')->order('create_time')->limit('5')->fetchSql(true)->delete(); 

thinkphp5

Db::table('think_user')->where('id',1)->delete();	//根据条件删除
db('user')->delete(1);	//【助手函数db】根据主键删除

#模型删除
$user = User::get(1);
$user->delete();
User::destroy(1);	//静态方法

6、链式操作

tp3.2和tp5中的连贯操作|链式操作是相似的,具体可查阅官方文档

#查询
Db::table('user')->where('status=1')->field('id,name')->limit(10)->select();
#新增
Db::table('user')->data($data)->insert()
#更新
Db::table('think_user')->where('id', 1)->update(['name' => 'thinkphp']);
#删除
Db::table('think_user')->limit(5)->order('create_time desc')->delete();

3.2、laravel

Laravel 支持四种数据库MySQL,PostgreSQL,SQLite,SQL Server。以下方法都是可以进行相关数据库交互的。

Laravel 的 Eloquent ORM 提供了一个漂亮、简洁的 ActiveRecord 实现来和数据库交互。每个数据库表都有一个对应的「模型」用来与该表交互。你可以通过模型查询数据表中的数据,以及在数据表中插入新记录。

1、执行sql语句

一旦配置好数据库连接后,便可以使用 DB facade 运行查询。 DB facade 为每种类型的查询提供了方法: select,update,insert,delete 和 statement。

$sql = “”; //原生sql
##以下方法是支持参数绑定和命名绑定的
#查询语句
$results = DB::select($sql);
#运行插入语句
DB::insert($sql );
#运行更新语句
$affected = DB::update($sql);
#运行删除语句
$deleted = DB::delete($sql);
#运行普通语句
DB::statement($sql);

2、查询数据

##查询构造器
#获取所有行
$users = DB::table('users')->get();
#获取单行
$user = DB::table('users')->where('name', 'John')->first();
#获取某个字段值
$email = DB::table('users')->where('name', 'John')->value('email');
#获取一列的值
$titles = DB::table('roles')->pluck('title');
#分块结果
DB::table('users')->orderBy('id')->chunk(100, function ($users) {
    foreach ($users as $user) {
        //
    }
});
#聚合方法 count, max,min, avg, sum
$users = DB::table('users')->count();
#判断记录是否存在
return DB::table('orders')->where('finalized', 1)->exists();
#select语句【查询指定字段】
$users = DB::table('users')->select('name', 'email as user_email')->get();

## Eloquent ORM
$userOrm = User::all();
#获取所有行
$users = User::where(‘id’,’>’,1)->get();
#获取单行
$users = User::where(‘id’,1)->first();
$user = User::find(1);		//主键检索【$users = User::find([1,2,3]);】
#获取某个字段值
$email = User::where(‘id’,1)->value(‘email’);

3、添加数据

##查询构造器
$data = ['email' => 'john@example.com', 'votes' => 0];
DB::table('users')->insert($data);
DB::table('users')->insertGetId($data);		//返回自增ID
DB::table('users')->insert([$data, $data1,$data2 ]); //插入多条记录

##Eloquent ORM
#要往数据库新增一条记录,先创建新模型实例,给实例设置属性,然后调用 save 方法
$user = new User;
$user->name = ‘laravel’;
$user->save();

4、更新数据

##查询构造器
DB::table('users')->where(‘id’,1)->update([‘votes’=>1]);
#自增或自减
DB::table('users')->increment('votes', 5);   //值+5【不填数字5则默认为1】
DB::table('users')->decrement('votes', 5);   //值-5【不填数字5则默认为1】

##Eloquent ORM
$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();
#批量更新
App\Flight::where('active', 1)->where('destination', 'San Diego')->update(['delayed' => 1]);

5、删除数据

###查询构造器
DB::table('users')->where('votes', '>', 100)->delete();
#清空表
DB::table('users')->truncate();

##Eloquent ORM
$flight = App\Flight::find(1);
$flight->delete();

App\Flight::destroy(1);	//主键删除,【多个:App\Flight::destroy([1, 2, 3]);】
$deletedRows = User::where('active', 0)->delete();	//条件查询删除

6、事务

```
【自动事务】transaction方法,第二个参数”5”表示事务发生死锁时重复执行的次数。
DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);

    DB::table('posts')->delete();
}, 5);		
【手动事务】
DB::beginTransaction();	//开始事务
DB::rollBack();			//回滚事务
DB::commit();			//提交事务
```

3.3、yii

1、执行sql语句

一旦你拥有了 DB Connection 实例,你可以按照下列步骤来执行 SQL 查询:

1、使用纯SQL查询来创建出 yii\db\Command;

2、绑定参数 (可选的);

3、调用 yii\db\Command 里 SQL 执行方法中的一个。

```
#返回多行. 每行都是列名和值的关联数组.【如果该查询没有结果则返回空数组】
$posts = Yii::$app->db->createCommand('SELECT * FROM post')->queryAll();

#返回一行 (第一行)【如果该查询没有结果则返回 false】
$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1')->queryOne();

#返回一列 (第一列)【如果该查询没有结果则返回空数组】
$titles = Yii::$app->db->createCommand('SELECT title FROM post')->queryColumn();

#返回一个标量值 【如果该查询没有结果则返回 false】
$count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post')->queryScalar();

##执行非查询语句
Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') ->execute();

```

2、查询数据

使用查询构建器通常包含以下两个步骤:

1、创建一个 yii\db\Query 对象来代表一条 SELECT SQL 语句的不同子句(例如 SELECT, FROM)。

2、执行 yii\db\Query 的一个查询方法(例如:all())从数据库当中检索数据。

注:yii的查询构造器感觉就像是直接在写sql语句

```
#返回所有行
// SELECT `id`, `email` FROM `user`
$rows = (new \yii\db\Query())
    ->select(['id', 'email'])
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->limit(10)
	->all();
#返回结果集的第一行
// SELECT * FROM `user` WHERE `username` LIKE `%test%`
$row = (new \yii\db\Query())->from('user')
    ->where(['like', 'username', 'test'])
	->one();
# column():返回结果集的第一列
#scalar():返回结果集的第一行第一列的标量值。

#exists():返回一个表示该查询是否包含结果集的值
#count():返回 COUNT 查询的结果。
// 执行 SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name
$count = (new \yii\db\Query())
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->count();
#其它集合查询方法:包括 sum($q), average($q), max($q), min($q) 等

#生成一条SQL语句,然后执行【在laravel中,也有相同的功能,toSql()】
$command = (new \yii\db\Query())
    ->select(['id', 'email'])
    ->from('user')
    ->where(['last_name' => 'Smith'])
    ->limit(10)
    ->createCommand();
    
// 打印 SQL 语句
echo $command->sql;
// 返回查询结果的所有行
$rows = $command->queryAll();
```

使用Active Record 查询数据。先定义类,定义 Active Record 类后,你可以从相应的数据库表中查询数据。大致步骤:

1、通过 yii\db\ActiveRecord::find() 方法创建一个新的查询生成器对象;

2、使用查询生成器的构建方法来构建你的查询;

3、调用查询生成器的查询方法来取出数据到 Active Record 实例中。

```
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::find()
    ->where(['id' => 123])
	->one();
$customer = Customer::findOne(123);

// SELECT * FROM `customer` WHERE `status` = 1 ORDER BY `id`
$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();
$customers = Customer::findAll([
    'status' => Customer::STATUS_INACTIVE,
]);
```

3、添加数据

```
##查询构造器
yii的查询构造器名副其实地只能查询,如果要新增、修改和删除的话,就需要生成sql语句然后执行。【目前是没看到官网上有写类似于tp和laravel查询构造器的insert、update和delete方法。】

##Active Record
// 插入新记录
$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save();
```

4、更新数据

```
##执行sql语句

##Active Record
// 更新已存在的记录
$customer = Customer::findOne(123);
$customer->email = 'james@newexample.com';
$customer->save();

//计数更新
$post = Post::findOne(100);
// UPDATE `post` SET `view_count` = `view_count` + 1 WHERE `id` = 100
$post->updateCounters(['view_count' => 1]);
```

5、删除数据

```
$customer = Customer::findOne(123);
$customer->delete();
Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]);	//删除多行
```

6、事务

```
【自动事务】
Yii::$app->db->transaction(function($db) {
    $db->createCommand($sql1)->execute();
    $db->createCommand($sql2)->execute();
    // ... executing other SQL statements ...
});

$customer = Customer::findOne(123);
Customer::getDb()->transaction(function($db) use ($customer) {
    $customer->id = 200;
    $customer->save();
    // ...其他 DB 操作...
});

【手动事务】
$db = Yii::$app->db;
$transaction = $db->beginTransaction();
try {
    $db->createCommand($sql1)->execute();
    $db->createCommand($sql2)->execute();
    // ... executing other SQL statements ...
    $transaction->commit();
} catch(\Exception $e) {
    $transaction->rollBack();
    throw $e;
} catch(\Throwable $e) {
    $transaction->rollBack();
    throw $e;
}

$transaction = Customer::getDb()->beginTransaction();
try {
    $customer->id = 200;
    $customer->save();
    // ...other DB operations...
    $transaction->commit();
} catch(\Exception $e) {
    $transaction->rollBack();
    throw $e;
} catch(\Throwable $e) {
    $transaction->rollBack();
    throw $e;
}
```

3.4、小结-模型

关于数据库和ORM方面,框架里面还有很多细节,例如数据库连接、分布式数据库连接与操作、数据库模式操作、关联关系、数据库迁移、实体与表的相互对应和生成等等都不再详细累述。

thinkphp、laravel、yii在数据访问层(Database Access Objects 简称DAO)所提供的API以及查询构造器的语法和使用上有差别【M()方法,Db::table,DB::table以及Yii$app->db】。但是在ORM或者说ActiveRecord,却是大同小异的。

例如修改用户1的信息:

```
$updataInfo = [‘name’=>’new_name’,’age’=>20,’sex’=>’男’];

#tp3.2的写法
$user = M(‘user’);
$user->where(“ id = 1”)->save($updataInfo);

#tp 5 的写法
Db::table('user')->where('id', 1)->update($updataInfo);

#laravel 的写法
DB::table('user')->where('id', 1)->update($updataInfo);

#yii 的写法 
Yii::$app->db->createCommand()->update('user', $updataInfo, ' id = 1 ')->execute();

#ORM或者ActiveRecord
User::where(‘id’,1)->update($updataInfo);	//支持静态方法的
$user = User::find(1);			//直接实例化对象
$user->name = ‘new_name’;
$user->save();
```

ORM的技术特点:

1、由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。

2、ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。

4、路由与控制器

路由就是一个配置,相当于中转。通过url访问网站时,路由会获取当前url,然后根据路由中的规则,执行相应的controller+action。

4.1、thinkphp

thinkphp 3.2的路由

1、标准格式URL

入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作。

http://serverName/index.php/模块/控制器/操作

2、URL大小写

ThinkPHP框架的URL是区分大小写(主要是针对模块、控制器和操作名,不包括应用参数)的,这一点非常关键,因为ThinkPHP的命名规范是采用驼峰法(首字母大写)的规则,而URL中的模块和控制器都是对应的文件,因此在Linux环境下面必然存在区分大小写的问题。

【windows不区分大小写,而linux区分,很容易导致上线访问不存在的情况】

3、URL模式

ThinkPHP支持的URL模式有四种:普通模式、PATHINFO、REWRITE和兼容模式,可以设置URL_MODEL参数改变URL模式。

```
普通模式: http://localhost/?m=home&c=user&a=login&var=value
PATHINFO模式:http://localhost/index.php/home/user/login/var/value/ 
REWRITE模式: 是在PATHINFO模式的基础上添加了重写规则的支持,可以去掉URL地址里面的入口文件index.php,但是需要额外配置WEB服务器的重写规则
兼容模式是用于不支持PATHINFO的特殊环境
```

4、路由定义与规则

//开启路由

‘URL_ROUTER_ON’  => true,

在模块的配置文件中使用URL_ROUTE_RULES参数进行配置,配置格式是一个数组,每个元素都代表一个路由规则

```
'URL_ROUTE_RULES'=>array(
    'news/:year/:month/:day' => array('News/archive', 'status=1'),
    'news/:id'              => 'News/read',
    'news/read/:id'         => '/news/:1',
),
```

thinkphp 5 的路由

1、标准格式URL

ThinkPHP5.0在没有启用路由的情况下典型的URL访问规则是:

```
http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]
```

注意:tp5.0取消了URL模式的概念,并且普通模式的URL访问不再支持,但参数可以支持普通方式传值

2、URL大小写

默认情况下,URL是不区分大小写的,也就是说 URL里面的模块/控制器/操作名会自动转换为小写。

3、路由定义与规则

```
use think\Route;
// 注册路由到index模块的News控制器的read操作
Route::rule('new/:id','index/News/read',’GET|POST’);

//不同请求类型定义路由规则的简化方法
Route::get('new/:id','News/read'); // 定义GET请求路由规则
Route::post('new/:id','News/update'); // 定义POST请求路由规则
Route::put('new/:id','News/update'); // 定义PUT请求路由规则
Route::delete('new/:id','News/delete'); // 定义DELETE请求路由规则
Route::any('new/:id','News/read'); // 所有请求都支持的路由规则

//批量注册
Route::rule([
'路由规则1'=>'路由地址和参数',
'路由规则2'=>['路由地址和参数','匹配参数(数组)','变量规则(数组)']
...
],'','请求类型','匹配参数(数组)','变量规则');
```

总结来说,thinkphp 的路由访问如果没有指定需求,是不需要手动配置的。当开发人员写好控制器和控制器方法的时候,就可以访问看到效果了。更多细节【如路由参数、资源路由、域名路由等】请参考tp的官方文档

thinkphp3.2的控制器

一般来说,ThinkPHP的控制器是一个类,而操作则是控制器类的一个公共方法。

thinkphp3.2 的控制器文件命名规范是:

 IndexController.class.php,驼峰法命名【首字母大写】+Controller.class,且文件放于Controller文件夹下面。简单示例:

```
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
    public function hello(){
        echo 'hello,thinkphp!';
    }
}
```

ThinkPHP的控制器支持多层和多级

多层指的是控制器可以分层,例如除了默认的Controller控制器层(我们可以称之为访问控制器),还可以添加事件控制器

```
├─Controller 访问控制器
│ ├─UserController.class.php 
│ ├─BlogController.class.php
│ ...
├─Event 事件控制器
│ ├─UserEvent.class.php 
│ ├─BlogEvent.class.php
│ ...

```

多级控制器是指控制器可以通过子目录把某个控制器层分组存放,首先需要设置控制器的分级层次

先设置配置:’CONTROLLER_LEVEL’     => 2,

然后

```
├─Controller 访问控制器
│ ├─User User分级(组)
│ │ ├─UserTypeController.class.php 
│ │ ├─UserAuthController.class.php 
│ ...
│ ├─Admin Admin分级(组)
│ │ ├─UserController.class.php 
│ │ ├─ConfigController.class.php 
│ ...
#通过URL访问就是:
http://serverName/Home/User/UserType
http://serverName/Home/Admin/User
```

URL生成

规则:U(‘地址表达式’,[‘参数’],[‘伪静态后缀’],[‘显示域名’])

```
#如果不定义模块的话 就表示当前模块名称
U('User/add') // 生成User控制器的add操作的URL地址
U('Blog/read?id=1') // 生成Blog控制器的read操作 并且id为1的URL地址
U('Admin/User/select') // 生成Admin模块的User控制器的select操作的URL地址
```

AJAX返回

ThinkPHP可以很好的支持AJAX请求,系统的\Think\Controller类提供了ajaxReturn方法用于AJAX调用后返回数据给客户端。并且支持JSON、JSONP、XML和EVAL四种方式给客户端接受数据,并且支持配置其他方式的数据格式返回。

```
#默认配置采用JSON格式返回数据(通过配置DEFAULT_AJAX_RETURN进行设置),我们可以指定格式返回
$data['status'] = 1;
$data['content'] = 'content';
$this->ajaxReturn($data,’xml’);
```

thinkphp5的控制器

tp5的控制器相比tp3.2简化了很多,看起来更像是一个简单的类。

ThinkPHP V5.0的控制器定义比较灵活,可以无需继承任何的基础类,也可以继承官方封装的\think\Controller类或者其他的控制器类。

```
namespace app\index\controller;
class Index 
{
    public function index()
    {
        return 'index';
    }
}
```

tp5 多级控制器和分层控制器和tp3.2类似

```
#助手函数
$event = controller('Admin/Blog', 'event');	//【支持跨模块调用】
echo action('Blog/update', ['id' => 5], 'event');
```

4.2、laravel

参考文档: https://learnku.com/docs/laravel/5.8/routing/3890 

默认路由文件

所有的 Laravel 路由都在 routes 目录中的路由文件中定义,这些文件都由框架自动加载。routes/web.php 文件用于定义 web 界面的路由。这里面的路由都会被分配给 web 中间件组,它提供了会话状态和 CSRF 保护等功能。定义在 routes/api.php 中的路由都是无状态的,并且被分配了 api 中间件组。

路由的Providers文件在项目构建的时候会在 app/Providers 里面自动生成:

```
#RouteServiceProvider.php 文件
路由的设计和全局配置基本都在这个文件中。【具体代码请参考项目】
Route::prefix('api')				//定义路由组的前缀
      ->middleware('api')		//定义路由组的中间件【这个在 app/Http/Kernel.php项目内核配置文件中的$middlewareGroups 配置 】
      ->namespace($this->namespace)		//命名空间
      ->group(base_path('routes/api.php'));	//路由组的路由文件【文件里面就是下面讲的路由设置】
```

laravel 路由

构建基本的路由只需要一个 URI 与一个 闭包:

```
Route::get('foo', function () {
    return 'Hello World';
});

#路由器允许你注册能响应任何 HTTP 请求的路由
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

#注册一个可响应多个 HTTP 请求的路由
Route::match(['get', 'post'], '/', function () {
    //这个是响应 get和post
});

Route::any('foo', function () {
    //这个是响应所有的HTTP请求类型的
});

#路由重定向
Route::redirect('/here', '/there');
Route::redirect('/here', '/there', 301);	//返回状态码

#视图路由
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);

#路由参数
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //这里接收的是多个参数【路由的参数通常都会被放在 {} 内,并且参数名只能为字母,同时路由参数不能包含 - 符号,如果需要可以用下划线 (_) 代替。路由参数会按顺序依次被注入到路由回调或者控制器中,而不受回调或者控制器的参数名称的影响】
});

#可选参数
Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

#正则表达式
Route::get('user/{id}/{name}', function ($id, $name) {
    //
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

#路由命名
Route::get('user/profile',’IndexController@index’)->name('profile');
#生成指定路由
$url = route('profile');
return redirect()->route('profile',['id' => 1]);		//重定向

#路由组和中间件

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // // 使用 first 和 second 中间件
    });

    Route::get('user/profile', function () {
        // // 使用 first 和 second 中间件
    });
});

#路由命名空间、路由前缀和子域名路由
Route::domain('{account}.myapp.com')->prefix(‘admin’)->namespace('Admin')->group(function () {
	// domain 定义子域名路由、prefix定义路由前缀、namespace定义命名空间
});

#访问当前路由
$route = Route::current();
$name = Route::currentRouteName();
$action = Route::currentRouteAction();
```

laravel 控制器

为了替代在路由文件中以闭包形式定义的所有的请求处理逻辑,一般使用控制类来组织这些行为。控制器被存放在 app/Http/Controllers 目录中。

```
#基础控制器
    <?php

    namespace App\Http\Controllers;

    use App\User;
    use App\Http\Controllers\Controller;

    class UserController extends Controller
    {
        /**
         * 显示给定用户的概要文件.
         *
         * @param int $id
         * @return View
         */
        public function show($id)
        {
            return view('user.profile', ['user' => User::findOrFail($id)]);
        }
	}

#定义一个指向控制器行为的路由:
Route::get('user/{id}', 'UserController@show');

#控制器分配中间件
Route::get('profile', 'UserController@show')->middleware('auth');
```

资源控制器

Laravel 资源路由将典型的「CURD (增删改查)」路由分配给具有单行代码的控制器

使用Artisan命令快速创建资源控制器:

```
php artisan make:controller PhotoController --resource
```

这个命令会生成一个控制器 app/Http/Controllers/PhotoController.php。 其中包括每个可用资源操作的方法。

接下来,你可以给控制器注册一个资源路由:

```
Route::resource('photos', 'PhotoController');
```

资源控制器操作处理【这就是一个比较简单的RESTful API 了】

4.3、yii

参考文档: https://www.yiichina.com/doc/guide/2.0/runtime-routing 

https://www.yiichina.com/doc/guide/2.0/structure-controllers

yii 路由

终端用户通过所谓的路由寻找到动作,路由是包含以下部分的字符串:

模块ID: 仅存在于控制器属于非应用的模块;

控制器ID: 同应用(或同模块如果为模块下的控制器) 下唯一标识控制器的字符串;

操作ID: 同控制器下唯一标识操作的字符串。

格式:

```
ModuleID/ControllerID/ActionID
【如果用户的请求地址为 http://hostname/index.php?r=site/index, 会执行site 控制器的index 操作】
```

当入口脚本在调用 run() 方法时,它进行的第一个操作就是解析输入的请求,然后实例化对应的控制器动作处理这个请求。 该过程就被称为引导路由(routing)。 路由相反的操作会将给定的路由和参数生成一个可访问的URL地址, 这个操作叫做创建URL。 创建出来的URL被请求的时候,路由处理器可以解析成原始的路由信息和参数。

负责路由解析和创建URL的组件是 URL管理器, URL管理器在程序组件中被注册成 urlManager。 URL管理器 提供方法 parseRequest() 来 解析请求的URL并返回路由信息和参数, 方法 createUrl() 用来根据提供的路由和参数创建一个可访问的URL。

在程序配置中配置 urlManager 组件,可以让你的应用不改变现有代码的情况下 识别任意的URL格式。 例如使用下面的代码创建一个到 post/view 控制器的 URL:

```
use yii\helpers\Url;
// Url::to() 将调用 UrlManager::createUrl() 来创建URL
$url = Url::to(['post/view', 'id' => 100]);
```

##路由处理包含两个步骤:

1、请求被解析成一个路由和关联的参数;

2、路由相关的一个控制器动作被创建出来处理这个请求。

##创建URLs

Yii提供了一个助手方法yii\helpers\Url::to(),用来根据提供的路由和参数创建各种各样的URL

```
use yii\helpers\Url;

// 创建一个普通的路由URL:/index.php?r=post%2Findex
echo Url::to(['post/index']);

// 创建一个带路由参数的URL:/index.php?r=post%2Fview&id=100
echo Url::to(['post/view', 'id' => 100]);

// 创建一个带锚定的URL:/index.php?r=post%2Fview&id=100#content
echo Url::to(['post/view', 'id' => 100, '#' => 'content']);

// 创建一个绝对路径URL:http://www.example.com/index.php?r=post%2Findex
echo Url::to(['post/index'], true);

// 创建一个带https协议的绝对路径URL:
https://www.example.com/index.php?r=post%2Findex
echo Url::to(['post/index'], 'https');
```

##使用美化的URL

要使用美化的URL,像下面这样在应用配置中配置urlManager组件:

```
[
    'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'enableStrictParsing' => false,
            'rules' => [
                'PUT,POST post/<id:\d+>' => 'post/update',
    			'DELETE post/<id:\d+>' => 'post/delete',
	    'post/<id:\d+>' => 'post/view',
	'<controller:(post|comment)>/create' => '<controller>/create',
	'<controller:(post|comment)>/<id:\d+>/<action:(update|delete)>'=> '<controller>/<action>',
	'<controller:(post|comment)>/<id:\d+>' => '<controller>/view',
	'<controller:(post|comment)>s' => '<controller>/index',
            ],
        ],
    ],
]
```

yii 控制器

控制器是 MVC 模式中的一部分, 是继承yii\base\Controller类的对象,负责处理请求和生成响应。 具体来说,控制器从应用主体 接管控制后会分析请求数据并传送到模型, 传送模型结果到视图,最后生成输出响应信息。

```
namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        return $this->render('view', [
            'model' => $model,
        ]);
    }

    public function actionCreate()
    {
        $model = new Post;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}
在操作 view (定义为 actionView() 方法)中, 代码首先根据请求模型ID加载 模型, 如果加载成功,会渲染名称为view的视图并显示,否则会抛出一个异常。

在操作 create (定义为 actionCreate() 方法)中, 代码相似. 先将请求数据填入模型, 然后保存模型,如果两者都成功,会跳转到ID为新创建的模型的view操作, 否则显示提供用户输入的create视图。
```

4.4、小结-路由与控制器

路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动。

控制器(controller)是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据

tp、laravel、yii基本上都实现了路由的基础寻址、注册、规则定义和URL生成。

其标准格式路由:

	http://serverName/index.php/模块/控制器/操作

5、视图

View视图代表模型包含的数据的可视化,是指用户看到并与之交互的界面。

tp、laravel、yii框架的视图文件都是在view目录下的。如果需要,可自行在相关配置文件中修改视图层名称。

5.1、thinkphp

定义规则

每个模块的模板文件是独立的,为了对模板文件更加有效的管理,ThinkPHP对模板文件进行目录划分,默认的模板文件定义规则是:

```
#tp3.2
视图目录/[模板主题/]控制器名/操作名+模板后缀
#tp5
视图目录/控制器名(小写)/操作名(小写)+模板后缀
```

默认的视图目录是模块的view目录,框架的默认视图文件后缀是.html

模板赋值

```
// 模板变量赋值
$this->assign('name','ThinkPHP');
$this->assign('email','thinkphp@qq.com');
// 或者批量赋值
$this->assign([
	'name' => 'ThinkPHP',
	'email' => 'thinkphp@qq.com'
]);
```

渲染规则

tp 3.2渲染模板输出最常用的是使用display方法,调用格式:

```
display('[模板文件]'[,'字符编码'][,'输出类型'])
// 不带任何参数 自动定位当前操作的模板文件
$this->display();
//使用主题功能
$this->theme('blue')->display('User:edit'); 
```

tp5渲染模板最常用的是继承系统的控制器基类后调用fetch方法,调用格式:

```
fetch('[模板文件]'[,'模板变量(数组)'])
// 不带任何参数 自动定位当前操作的模板文件
return $this->fetch();
// 指定模板输出
return $this->fetch('edit'); 
```

5.2、laravel

Blade简介

Blade 是 Laravel 提供的一个简单而又强大的模板引擎。和其他流行的 PHP 模板引擎不同,Blade 并不限制你在视图中使用原生 PHP 代码。所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存起来,除非它被修改,否则不会重新编译,这就意味着 Blade 基本上不会给你的应用增加任何负担。Blade 视图文件使用 .blade.php 作为文件扩展名,被存放在 resources/views 目录。

向视图传递参数

使用laravel现有的View facade 进行视图的赋值和渲染

```
return view('greetings', ['name' => 'Victoria']);
```

5.3、yii

视图是 MVC 模式中的一部分。 它是展示数据到终端用户的代码,在网页应用中, 根据视图模板来创建视图,视图模板为PHP脚本文件, 主要包含HTML代码和展示类PHP代码,通过view应用组件来管理, 该组件主要提供通用方法帮助视图构造和渲染, 简单起见,我们称视图模板或视图模板文件为视图。–【来源:Yii 2.0权威指南-视图】

视图赋值-渲染

在 控制器 中,可调用以下控制器方法来渲染视图:

1、render(): 渲染一个 视图名 并使用一个 布局 返回到渲染结果。

2、renderPartial(): 渲染一个 视图名 并且不使用布局。

3、renderAjax(): 渲染一个 视图名 并且不使用布局, 并注入所有注册的JS/CSS脚本和文件,通常使用在响应AJAX网页请求的情况下。

4、别名下的视图文件。

在 小部件 中,可调用以下小部件方法来渲染视图:

1、render(): 渲染一个 视图名.

2、别名下的视图文件。

在视图中有两种方式访问数据:推送和拉取。

1、推送方式是通过视图渲染方法的第二个参数传递数据, 数据格式应为名称-值的数组, 视图渲染时,调用PHP extract() 方法将该数组转换为视图可访问的变量。

2、拉取方式可让视图从view component视图组件或其他对象中主动获得数据(如Yii::$app), 在视图中使用如下表达式$this->context可获取到控制器ID, 可让你在report视图中获取控制器的任意属性或方法。

例子:

```
// 渲染一个名为 "list" 的视图
return $this->render('list', [
    'items' => $this->items,
]);
```

5.4、小结-视图

上文只是简单介绍了各个框架视图的赋值和渲染,具体模板引擎的语法和模板标签等请参考官方文档。毕竟前后分离是大趋势,而且各个模板引擎的语法差异太大,学习起来无异于学习一门新的语言。

6、事件及监听器

6.1、第三方接口交互、短信发送及邮件发送

【这一节内容和框架无关】网站项目很多时候并不是孤立的,需要第三方提供相应的服务和接口。例如:对接短信网关来实现短信发送功能,对接OSS服务器【例如七牛云】实现文件存储功能,对接第三方支付整合进行线上支付功能等等。一般来说,第三方网站都是有提供相应的对接demo的,但也不排除只有接口文档或者demo不满足需求的情况。所以,还是得自行编写代码。一般情况,php使用CURL,就可以对接市面上绝大多数接口了。【很多demo也是使用CURL】

CURL是一个非常强大的开源库,支持很多协议,包括HTTP、FTP、TELNET等,我们使用它来发送HTTP请求。它给我们带来的好处是可以通过灵活的选项设置不同的HTTP协议参数,并且支持HTTPS。CURL可以根据URL前缀是“HTTP” 还是“HTTPS”自动选择是否加密发送内容。

第三方接口简单的请求示例

```
# ApiRequestService.php 类文件,核心方法postData,就是使用CURL发送post请求
<?php
namespace App\Service;	//命名空间
class ApiRequestService{
	private $key;       //key值密钥
	private $secret;	  //secret值密钥
	private $url;       //第三方接口请求地址
    private $method;   //第三方接口请求方法
    private $content;   //post请求数据
    public function __construct()
    {
        //构造函数--可以根据需求设置
    }

    public function getKey()
    {
        return $this->key;
	}
	
    public function setKey($key)
    {
        $this->key = $key;
    }
	//...
	#属性值的seter和getter
	//...
	
	//根据第三方请求封装的参数
    private function createData(){
        return array(			
            'Key' => $this->getKey(),
            'Method' => $this->getMethod(),
            'Timestamp' => date("Y-m-d H:i:s"),
            'Content' => $this->getContent(),
            'ClientVersion' => '1.0',
            'ApiVersion' => '1.0',
        );
    }
	
	//执行post方法
    public function postData(){
        $apiRequest = $this->getApiRequest();		
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->getUrl());
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
        // 设置POST方式发送数据
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $apiRequest);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //不验证证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //不验证证书
        $fs = curl_exec($ch);
        $afs = json_decode($fs, true);
        curl_close($ch);
        return $afs;
    }

    public function getApiRequest(){
        return json_encode($this->createData());
    }
}
```

短信发送

当网站能够请求第三方接口的时候,对接短信网关实现短信发送功能,也就没那么难了。市面上有很多的短信发送平台【俗称短信网关】的接口就像这样:

在项目中,一般的情况是需要把这些接口给封装的。因为像短信模板ID、签名、主账号ID甚至Key和secret密钥,这些东西都是与项目无关的。

短信发送类:

```
<?php
namespace App\Service;			//命名空间
use App\Service\ApiRequestService;	//上文的post请求发送类
use Illuminate\Support\Facades\Log;	//日志

class SendMsgService{
    private $mobile;		//发送的手机号码	
    private $templateType;	//模板ID
    private $parameter;	//内容参数
    private $code;			//验证码
    private $appUrl;		//请求地址
    private $appSecret;		//请求Secret密钥
	private $appId;		//应用id
	
	public function __construct()
	{
	        //构造函数--可以直接将静态配置生成
	}

	// 属性值 setter和getter方法
	...
	//
	
	//检验手机格式
    public function checkMobile(){
        //检验手机格式
    }

    /**
     * 发送验证短信
     * @return bool
     */
    public function sendMsg(){
        try {
	//这里是直接发送请求给短信网关【也可以根据需求调用第三方接口的demo代码】
            $content = [
                "Moblie" => $this->getMobile(),
                "TemplateType" => $this->getTemplateType(),
                "Parameter" =>$this->getParameter,
                "ValidCode"=>$this->getCode(),
            ];
            $central = new ApiRequestService();	//调用自定义的请求发送类
            $central->setUrl($this->getAppUrl());
            $central->setContent($content);
            $res = $central->postData();		//发送请求
            if(isset($res["Code"]) && $res["Code"] == 1){
                Log::info('success===>'.json_encode($res));
                //成功处理
                return true;
            }
            else{
                Log::error('error===>'.json_encode($res));
                //失败处理
                return false;
            }
        } catch (\Exception $e) {
            Log::error('error===========>'.$e->getMessage());
            return false;
        }
    }

}

```

调用第三方接口和短信网关其实原理很简单

1、根据密钥和参数生成请求内容。

2、发送请求内容到指定接口地址。

3、解析返回的内容。

稍微复杂点的,就可能需要生成签名,以及需要签名证书了。

邮件发送

网站实现邮件发送是很简单的。但是它和网站对接短信网关的实现原理就有所不同了。毕竟网站对接短信,只是调用短信平台的接口,并非真正地去做短信发送的功能。很多网上邮箱都是具备相应的POP3/SMTP服务的,只需要开启即可。

laravel框架有详细的描述邮件发送功能:

https://learnku.com/docs/laravel/5.8/mail/3920

如果是自己实现php的邮件发送功能的话,主要有两种方式:

1、PHP自带的mail()函数【略】

2、封装的smtp邮件发送类

推荐还是使用开源的PHPMailer: https://github.com/PHPMailer/PHPMailer 

无论是laravel框架实现,还是PHPMailer实现,都可以写成自己项目所需的类:

```
#邮件发送类
<?php
namespace App\Service;			//命名空间
use Illuminate\Support\Facades\Log;	//日志
class SendEmailService{
    private $email;		//发送的收件人
    private $templateType;	//模板ID
    private $parameter;	//内容参数
	private $code;			//验证码
	
	public function __construct()
	{
	        //构造函数--可以直接将静态配置生成
	}

	// 属性值 setter和getter方法
	...
	//
	
	//检验邮件格式
    public function checkEmail(){
        //检验邮件格式
    }

    /**
     * 发送邮件
     * @return bool
     */
    public function sendEmail(){
        try {
	//邮件发送代码
	
            //邮件发送代码end
            if(isset($res["Code"]) && $res["Code"] == 1){
                Log::info('success===>'.json_encode($res));
                //成功处理
                return true;
            }
            else{
                Log::error('error===>'.json_encode($res));
                //失败处理
                return false;
            }
        } catch (\Exception $e) {
            Log::error('error===========>'.$e->getMessage());
            return false;
        }
    }

}

```

小结备注

为什么会在事件及监听器这一章里面先插入【第三方接口、短信发送及邮件发送】这些内容呢?因为讲到thinkphp的Hook【钩子】、yii的Event【事件】就不可避免的要提及behavior【行为】这一概念,但是laravel在事件系统里面是没有“行为”这一概念的【对应的是监听器】。在编码上,behavior【行为】看着像是一种“特殊”的类。在上文中,无论是第三方接口还是各个功能模块,都是可以自行封装成一个类的。因此,做简化处理,在下文中的,就不再累述行为和监听器概念,直接以功能类表达。

6.2、thinkphp的Hook

行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以独立调用,也可以绑定到某个标签中进行侦听。

系统核心提供的标签位置包括下面几个(按照执行顺序排列):

在每个标签位置,可以配置多个行为定义,行为的执行顺序按照定义的顺序依次执行。行为定义: 通过Common\Conf\tags.php【tp3.2,tp5在APP_PATH目录或者模块目录下的tags.php】配置文件定义,格式如下:

```
return [
    'app_init'=> [
        'app\\index\\behavior\\CheckAuth',
        'app\\index\\behavior\\CheckLang'
    ],
    'register'=> [		//用户注册
        'app\\index\\behavior\\sendMsg’			//发送短信
    ]
]
```

#行为绑定

```
// 注册 app\index\behavior\sendMsg行为类到register标签位
Hook::add('register','app\\index\\behavior\\sendMsg); 
//批量行为
Hook::add('app_init',['app\\index\\behavior\\CheckAuth','app\\index\\behavior\\CheckLang','app\\admin\\behavior\\CronRun']);
```

#行为触发

```
#注意,必须实现run(&$param)方法,行为是通过这个方法执行的 
//在需要的地方-比如用户成功注册
Hook::listen('register',$param);
```

6.3、laravel的event与listen

事件类通常存放在 app/Events 目录下,而这些事件类的监听器则存放在 app/Listeners 目录下。

注册事件和监听器

Laravel 应用中的 EventServiceProvider 为注册所有的事件监听器提供了一个便利的场所。其中, listen 属性包含了所有事件 (键) 以及事件对应的监听器 (值) 的数组。

使用 event:generate 命令会生成在 EventServiceProvider 中列出的所有事件和监听器。

```
protected $listen = [
    Registered::class => [	//用户注册事件
        SendEmailVerificationNotification::class,	//邮件发送
	‘App\Listeners\SendMsgListener’,			//短信发送【写法和上面的不一样,效果是相同的】
	],
	
];
# php artisan event:generate Registered   //会在Event目录下生成事件文件
# php artisan event:listener SendMsgListener		//会在listeners目录下生成监听器文件
```
也可以在EventServiceProvider 的boot方法中手动注册事件
```
public function boot()
{
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //执行代码
    });
}
``

定义事件

比如用户注册

```
<?php
namespace App\Events;
use App\User;
use Illuminate\Queue\SerializesModels;		//队列序列化
class Registered
{
    use SerializesModels;
    public $user;			//这个是事件的属性值,在监听器中可以访问
    /**
     * 创建一个事件实例。
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }
}
```

定义监听器

短信发送

```
<?php
namespace App\Listeners;
use App\Events\Registered;
class SendMsgListener
{
    /**
     * 创建事件监听器。
     */
    public function __construct()
    {
    }

    /**
     * 处理事件。
     */
    public function handle(OrderShipped $event)
    {
        // 使用 $event->user来访问 user...
	//发送短信
	}
	
## 有时候我们需要一个监听器处理多个事件--比如发送短信,用户注册需要发送-登录或注销也需要发送,这时可以使用事件订阅者【也是一个监听器】来处理
    /**
     * 处理用户登录事件。
     */
    public function onUserLogin($event) {}

    /**
     * 处理用户注销事件。
     */
    public function onUserLogout($event) {}
	    /**
	     * 为订阅者注册监听器
	     */
	    public function subscribe($events)
	    {
	        $events->listen(
	            'Illuminate\Auth\Events\Login',
	            'App\Listeners\SendMsgListener@onUserLogin'
	        );
	
	        $events->listen(
	            'Illuminate\Auth\Events\Logout',
	            'App\Listeners\SendMsgListener@onUserLogout'
	        );
	    }
}

##编写好事件订阅者后,需要在 EventServiceProvider 中的 $subscribe 属性中注册订阅者
    protected $subscribe = [
        'App\Listeners\SendMsgListener,
    ];
```

分发事件

如果要分发事件,你可以将事件实例传递给辅助函数 event 。该辅助函数将会把事件分发到所有该事件相应的已经注册了的监听器上。 event 辅助函数可以全局使用,你可以在应用中的任何位置进行调用:

```
public function store(){
	//代码逻辑--用户成功注册了
	//代码逻辑中是不涉及到短信发送-邮件发送等代码的。
	event(new Registered($user));		//Registered是事件的类--给用户发短信
}
```

备注: 事件的作用其实可以被一个方法代替,在需要调用的地方引用一下,然后执行这个方法就行了。laravel中事件可以使用队列来处理,可以用来操作那些比较耗时的操作,例如发送邮件,发送验证码等等。

6.4、yii的event

事件可以将自定义代码“注入”到现有代码中的特定执行点。 附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。 例如,邮件程序对象成功发出消息时可触发 messageSent 事件。 如想追踪成功发送的消息,可以附加相应追踪代码到 messageSent 事件。

事件处理器

事件处理器是一个PHP 回调函数, 当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一:

字符串形式指定的 PHP 全局函数,如 ‘trim’ ;

对象名和方法名数组形式指定的对象方法,如 [$object, $method] ;

类名和方法名数组形式指定的静态类方法,如 [$class, $method] ;

匿名函数,如 function ($event) { … } 。

附加事件处理器

调用 yii\base\Component::on() 方法来附加处理器到事件上

```
$foo = new Foo;
// 处理器是全局函数
$foo->on(Foo::EVENT_HELLO, 'function_name',$p); //$p 参数可选,使用$event->data;访问参数
// 处理器是对象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// 处理器是静态类方法
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 处理器是匿名函数
$foo->on(Foo::EVENT_HELLO, function ($event) {
    //事件处理逻辑
});

```

备注: 从事件移除处理器,调用 yii\base\Component::off() 方法

触发事件

事件通过调用 yii\base\Component::trigger() 方法触发,此方法须传递事件名, 还可以传递一个事件对象,用来传递参数到事件处理器

```
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
    public $message;
}
class Mailer extends Component
{
    const EVENT_MESSAGE_SENT = 'messageSent';
    public function send($message)
    {
        // ...发送 $message 的逻辑...

        $event = new MessageEvent;
        $event->message = $message;
        $this->trigger(self::EVENT_MESSAGE_SENT, $event);
    }
}
```

6.5、小结-事件及监听器

为什么要把【钩子】事件及监听器作为框架基础笔记中的一章。其一,这些东西确实不是难点。其二、是想时时刻刻提醒自己,写代码要有解耦的习惯。一个控制器或者模型,动辄上千行的代码,全是复制粘贴“短信发送”、“邮件发送”、“调用第三方接口”等功能性代码,自己维护起来都很老火,更别说别人了。如果确实不喜欢用事件和监听器,把重复代码自己提炼成方法也是可以的。

7、摘抄后记

首先,本文只是知识点摘抄、学习笔记,并不涉及到项目的实践应用,所以部分代码并没有什么逻辑可言。其次,文章字数看起来很多,其实大多都是代码。最后,回忆一下,本文主要讲的是thinkphp、laravel、yii这三个框架的基本目录层次、数据库与ORM、路由与控制器、视图、框架的事件等知识点和比较。并顺带记录了一下php的第三方接口交互、短信发送和邮件发送。