MVC、MVP和MVVM

基本概念参考这篇MVC,MVP 和 MVVM 模式如何选择?,已经说的比较清楚了。

由于ThinkPHP5在整体的架构上是拥抱MVVM的,因此在模板引擎方面并没有特别改进,但同样满足MVCMVP模式开发。

命名空间

命名空间是学习ThinkPHP5的必备基础,用一句话说就是:把类、函数、变量 等放到逻辑子文件夹中去,以避免命名冲突。关于命名空间的解释可以看这篇文章,讲的比较通俗易懂。

Composer

Composer是PHP的依赖关系管理工具,对原有的开发模式和类库扩展机制是一种颠覆。原本你无论是下载还是GIT获取,一般都只能手动更新,而无法做到对依赖类库的自动更新(虽然Git可以很方便更新,但仍然无法完成依赖更新),composer提供了一种全新的安装和更新方式,目前主流框架和类库都已经支持composer安装方式了,使用简单的命令行操作就可以安装和更新类库,需要PHP5.3.2+版本,支持Linux和Windows平台。

Composer还提供了一个自动加载的入口文件,用于实现composer依赖安装的类库自动加载。ThinkPHP5并没有使用Composer的自动加载入口,而是自己封装了对Composer类库的自动加载支持。

由于composer的访问都在国外,因此国内的安装和更新速度有影响,不过可以使用国内镜像。

入口文件

和大多数主流框架一样,ThinkPHP5采用单一入口(一般而言就是我们经常看到的index.php文件,通过URL重写可以隐藏)进行URL或者接口访问,这个文件我们就称之为应用入口文件(在ThinkPHP5中也是唯一开放对外访问的程序文件),但你仍然可以增加新的入口文件,例如admin.phpapi.php等入口文件,主要用于绑定某个模块或者使用特殊的命令行操作等。

模块/控制器/操作

ThinkPHP5的最小访问单元是操作,而每个操作其实就是一个控制器类的公共方法,模块在ThinkPHP5里面并没有实际的意义,仅仅是用于控制器类的命名空间区分,或者说是一些控制器的聚合,并且支持模块单独的配置参数。所以ThinkPHP5是允许单一模块(其实是没有模块的概念了)应用存在的。

路由

路由是用于规划(一般同时也会进行简化)请求的访问地址,在访问地址和实际操作方法之间建立一个路由规则 => 路由地址的映射关系。

ThinkPHP5并非强制使用路由,如果没有定义路由,则可以直接使用“模块/控制器/操作”的方式访问,如果定义了路由,则该路由对应的路由地址就被不能直接访问了。一旦开启强制路由参数,则必须为每个请求定义路由(包括首页)。

使用路由有一定的性能损失,但随之也更加安全,因为每个路由都有自己的生效条件,如果不满足条件的请求是被过滤的。你远比你在控制器的操作中进行各种判断要实用的多。

HTTP协议

HTTP(HyperText Transfer Protocol)是一套计算机通过网络进行通信的规则。使HTTP客户(如Web浏览器)能够从HTTP服务器(Web服务器)请求信息和服务,HTTP1.1协议是一种无状态的协议,无状态是指Web浏览器和Web服务器之间不需要建立持久的连接,这意味着当一个客户端向服务器端发出请求,然后Web服务器返回响应(response),连接就被关闭了,在服务器端不保留连接的有关信息,HTTP2.0做了很多的改进,但尚未普及。

HTTP遵循请求(Request)/应答(Response)模型。Web浏览器向Web服务器发送请求,Web服务器处理请求并返回适当的应答。所有HTTP连接都被构造成一套请求和应答。

请求(Request

这里的请求是指HTTP请求,一个HTTP请求信息由3部分组成:

  • 第一行是请求方法URI协议/版本
  • 第二部分是请求头(Request Header)
  • 第三部分是请求内容正文

请求头和内容之间用空行隔开,如果是GET或者HEAD请求的话,是不包含请求内容的,在ThinkPHP5中,使用think\Request对象来统一获取请求数据。

响应(Reponse

这里的响应指的是HTTP响应,HTTP响应也由3个部分构成:

  • 第一行是协议状态版本代码描述
  • 第二行开始是响应头(Response Header)
  • 第三部分是响应内容正文

响应头和响应正文之间用空行隔开,在ThinkPHP5中,使用think\Response对象完成请求响应操作。

请求类型

HTTP支持的请求类型有很多,常用的类型及作用如下:

请求类型描述
GET通过请求URI得到资源
POST用于添加新的资源
PUT用于修改某个资源
PATCH修改资源的部分内容
DELETE删除某个资源
OPTIONS询问可以执行哪些方法
HEAD类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据

HTTP状态码

HTTP状态码也称为应答码,它反映了Web服务器处理HTTP请求状态。HTTP应答码由3位数字构成,其中首位数字定义了应答码的类型:

  • 1XX-信息类:表示收到Web浏览器请求,正在进一步的处理中;
  • 2XX-成功类:表示用户请求被正确接收,理解和处理例如:200 OK;
  • 3XX-重定向类:表示请求没有成功,客户必须采取进一步的动作,如 301重定向;
  • 4XX-客户端错误:表示客户端提交的请求有错误 例如:404 NOT Found,意味着请求中所引用的文档不存在;
  • 5XX-服务器错误:表示服务器不能完成对请求的处理:如 500;

对于Web开发人员来说掌握HTTP应答码有助于提高Web应用程序调试的效率和准确性,在接口开发的时候也应该养成返回和判断正确的状态码的习惯。

RESTFul

RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

REST (英文:Representational State Transfer,资源表现层转化)指的是一组架构约束条件和原则,满足这些约束条件和原则的应用程序或设计就是 RESTful。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

数据库和模型

ThinkPHP内置实现了数据库抽象访问层,并通过Db类进行统一数据操作。而模型则是基于数据库操作而进行的数据和业务封装,采用对象化的方式更直观和方便进行数据及逻辑操作。

数据库Db类访问的数据类型一般而言是数组类型,而模型操作返回的数据是模型的对象实例,模型比数据库操作增加了更多的数据自动处理操作,包括修改器和获取器等等,并且具备事件和关联机制,完成数据库Db类不能或者较难完成的操作。

ORM

ORM(Object-Relational Mapping 对象关系映射)提供了概念性的、易于理解的模型化数据的方法,用于实现面向对象编程语言里面不同类型系统的数据之间的转换。ORM方法论基于三个核心原则:

  • 简单:以最基本的形式建模数据。
  • 传达性:数据库结构被任何人都能理解的语言文档化。
  • 精确性:基于数据模型创建正确标准化的结构。

ORM提供的不只是描述不同对象间关系的一个简单而直接的方式,还提供了灵活性。使用ORM创建的模型比使用其它方法创建的模型更有能力适应系统的变化,数据模型能自动映射到正确标准化的数据库结构。

ThinkPHP的ORM使得查询数据变得非常的简单。

ActiveRecord

Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。

Active Record 本身的设计适合非常简单的领域需求,对于复杂的模型尤其是模型关联缺少足够的支持,但ThinkPHP5的模型操作对于模型关联也提供了强有力的支持。

闭包

闭包也就是PHP5.3开始引入的匿名函数,允许创建一个没有名称的函数,并且支持函数参数以及引用外部变量,ThinkPHP5很多功能可以支持闭包功能来简化代码,尤其是在路由定义和数据查询中经常被用到,例如:

return [
    // 使用闭包定义路由
    'hello/[:name]' => function ($name) {
        return 'Hello,' . $name . '!';
    },
];

查询中使用闭包:

$users = User::all(function ($query)  use($id,$name){
    $query->where('name', 'like', $name)
        ->where('id', '>', $id)
        ->limit(10);
});

行为和钩子

ThinkPHP中的行为是一个比较抽象的概念,你可以把行为想象成在应用执行过程中的一个动作。在框架的执行流程中,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,把这些行为抽离出来的目的是为了让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。

而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为钩子,当应用程序运行到这个钩子的时候,就会被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给某一个钩子绑定相关行为就成了一种类AOP编程的思想。

一个钩子可以注册多个行为,执行到某个钩子位置后,会按照注册的顺序依次执行相关的行为。但在某些特殊的情况下,你可以设置某个钩子只能执行一次行为,又或者你可以在一个钩子的某个行为中返回false来强制终止后续的行为执行;一个行为可以同时注册到多个不同的钩子上,完全看应用的需求来设计。

事件

ThinkPHP5中的事件一般是指数据库操作和模型操作在完成数据写入之后的回调机制。

数据库操作的回调也称为查询事件,是针对数据库的CURD操作而设计的回调方法,主要包括:

事件描述
before_selectselect查询前回调
before_findfind查询前回调
after_insertinsert操作成功后回调
after_updateupdate操作成功后回调
after_deletedelete操作成功后回调

模型事件可以看成是模型层的钩子和行为,只不过钩子的位置主要针对模型数据的写入操作,包含下面这些:

钩子对应操作快捷注册方法
before_insert新增前beforeInsert
after_insert新增后afterInsert
before_update更新前beforeUpdate
after_update更新后afterUpdate
before_write写入前beforeWrite
after_write写入后afterWrite
before_delete删除前beforeDelete
after_delete删除后afterDelete

before_writeafter_write表示无论是新增还是更新都会执行的钩子。

依赖注入

在软件工程领域,依赖注入(Dependency Injection,简称DI)是用于实现控制反转(Inversion of Control,简称IoC)的最常见的方式之一,而控制反转的目的是为了更好的解耦。

ThinkPHP5的依赖注入主要指针对访问控制器的依赖注入,实现方式主要包括架构函数注入和操作方法注入,表现方式则是在控制器架构函数和操作方法中一旦对参数进行对象类型约束则会自动触发依赖注入(再通俗点说就是自动实例化该对象),由于访问控制器的参数都来自于URL请求,普通变量就是通过参数绑定自动获取,对象变量则是通过依赖注入生成。

Trait

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

ThinkPHP5中的Trait主要用于模型和控制器功能的扩展和复用。

请求缓存

ThinkPHP5默认内置了请求缓存功能,该功能的设计初衷就是减少频繁的相同请求的服务器开销,提高性能。其原理是一旦开启请求缓存后(可以是单个请求,也可以是全局请求),第一次请求完成后(而且必须是一个成功请求)会把响应数据通过一个唯一的缓存标识缓存起来并发送HTTP缓存,下一次请求的时候如果是相同的缓存标识会判断是否存在HTTP缓存如果存在则直接读取浏览器缓存数据,如果没有HTTP缓存则直接读取响应缓存内容而不需要调用真正的请求,不过请求缓存仅对GET请求有效。

获取器

获取器的作用是对模型的数据对象的(原始)数据做出自动处理。一个获取器对应模型的一个特殊方法,方法格式为:

getFieldNameAttr

FieldName为数据表字段的驼峰转换,定义了获取器之后会在下列情况自动触发:

  • 模型的数据对象取值操作($model->field_name);
  • 模型的序列化输出操作($model->toArray());
  • 显式调用getAttr方法($this->getAttr('field_name'));

获取器的场景包括:

  • 时间日期字段的格式化输出;
  • 集合或枚举类型的输出;
  • 数字状态字段的输出;
  • 组合字段的输出;

修改器

和获取器相反,修改器的主要作用是对模型设置的数据对象值进行处理。修改器也是在模型中定义的一个特殊方法,方法的命名规范为:

setFieldNameAttr

修改器的使用场景和读取器类似:

  • 时间日期字段的转换写入;
  • 集合或枚举类型的写入;
  • 数字状态字段的写入;
  • 某个字段涉及其它字段的条件或者组合写入;

定义了修改器之后会在下列情况下触发:

  • 模型对象赋值;
  • 调用模型的data方法,并且第二个参数传入true
  • 调用模型的save方法,并且传入数据;
  • 显式调用模型的setAttr方法;
  • 定义了该字段的自动完成;

验证器

验证器对于一个验证需求和场景的统一封装,验证的数据字段和验证规则都定义在验证器里面,然后可以在控制器或者模型中调用验证器进行实际的验证,改变验证规则或者增加验证字段不需要修改控制器和模型的验证代码。