首页 > 编程笔记

PHP简介(非常详细)

PHP(Hypertext Preprocessor,超文本预处理器)是一种通用开源脚本语言,其语法吸收了C语言、Java 和 Perl 的特点,利于学习,使用广泛,主要适用于 Web 开发领域。

PHP 独特的语法混合了 C、Java、Perl 以及 PHP 自创的语法,它可以比 CGI 或者 Perl 更快速地执行动态网页。

与其他的编程语言相比,PHP 是将程序嵌入到 HTML(标准通用标记语言下的一个应用)文档中去执行,执行效率比完全生成 HTML 标记的 CGI 要高许多。

PHP 还可以执行编译后代码,编译可以达到加密和优化代码运行,使代码运行更快。

2015 年 6 月,官方发布了 PHP 7Alpha 1 版本,同年 12 月 3 日发布 GA 版本,PHP 7 的发布对于 PHP 来说是具有里程碑意义的。在性能上,PHP 7 的执行效率是原来 PHP 5 的两倍左右,和 HHVM 相当。

相对于 PHP 5.6.x,PHP 7 多了以下几个主要的新特性:
当然,PHP 7 相对于以前的版本还有很多不同之处,但是大部分是兼容以前版本的,所以大多情况下无须修改代码就可以迁移到 PHP 7。

我们首先介绍 PHP 语言的优势、运行机制和原理,以及 PHP 7 的新特性,帮助读者对 PHP 有一个初步的认识。

PHP 语言的优势

PHP 语言主要有以下几点优势:

1) PHP 学习入门快、开发成本低,语法相对简单,并且提供了丰富的类库,如用于图像处理的 GD 库、各种加密扩展(如 OpenSSL 和 Mcrypt 等),可以很方便地直接使用。

很多库默认在安装 PHP 环境的时候都是自带的。

2) PHP 结合 Linux、Nginx 或 Apache、MySQL 可以方便快捷地搭建一套系统,PHP 还支持直接调用系统命令,这样便可以用代码完成许多操作 Linux 的工作,如打包压缩、复制粘贴、重命名、执行 Linux 中 grep 查询筛选等。

Nginx 是一个非常优秀的 Web 服务器软件,Nginx 可接收客户端请求,将 PHP 文件发送给 PHP 程序执行,Nginx 中的 PHP 采用 fastCGI 的形式运行脚本。

3) PHP 支持使用 MySQL、MSSQL、SQLite 等多种数据库,其中与 MySQL 的结合使用最为流行。

PHP 提供了3种连接 MySQL 的扩展,包括 MySQL 扩展、MySQLi 扩展和 PDO 扩展,其中:
4) PHP 是解释执行的脚本语言,写完程序以后可以立即执行,不像 C、Java、C++ 等其他语言需要编译再执行,这使得 PHP 的开发效率更高。

5) PHP 中使用到的配置文件相对简单,与 PHP 运行有关的配置文件常用的有 php-fpm.conf 和 php.ini 两个,并且配置参数也简单易懂。

更改了 PHP 的配置文件不需要重新启动即可继续运行,因为 PHP 每次运行程序前都会主动加在配置文件中,这比 Java 等其他语言方便多了。

6) PHP 作为最流行、使用最为广泛的 Web 开发语言,有着丰富的生态圈,有许多著名的开源框架可供使用,例如:
基于这些优秀的开源系统,你可以方便快速地搭建一套 Web 站点。另外,活跃的社区氛围也能帮助你快速解决开发中遇到的问题。

7) 结合 LVS 负载均衡、消息队列、数据库主从等技术,PHP 能够支持一般大型网站的应用,满足绝大多数场景下的应用开发。

8) PHP 本身是由C语言开发的,在一些对性能有严苛要求的情况下,还可以使用C语言编写 PHP 的扩展来提升程序的执行速度,使用 PHP 完成主要业务的代码编写,使用C完成性能提升的需求,这使得可以保证软件开发效率的同时兼顾执行效率。

在这种对软件开发速度和程序执行性能有极致追求的情况下,如果是其他语言,可能会让你束手无策,或者推倒重来。

9) 国内的许多大公司,如百度、淘宝、360 等公司都广泛地使用 PHP 作为开发语言,在具体实践中已经取得了很大成功,有许多成功的经验可供借鉴。

PHP 的运行机制和原理

PHP 由内核 Zend 引擎和扩展层组成,其中:
Zend 引擎是用C语言实现的,将 PHP 代码通过词法语法解析成可执行的 opcode 并实现相应的处理方法、基本的数据结构内存分配和管理等,对外提供相应的可供调用的 API 方法。

Zend 引擎是 PHP 的核心,所有的外围功能都是围绕它实现的。扩展层通过组件的方式提供各种基础服务、内置函数,标准库都是通过它实现的。用户也可以编写自己的扩展来实现特定的需求。

服务端应用编程接口(Server Application Programming Interface,SAPI),通过一系列钩子函数使得 PHP 可以和外围交互数据。我们平时编写的 PHP 程序就是通过不同的 SAPI 方式得到不同的应用模式,如通过 WebServer 实现的 Web 应用和在命令行下运行的脚本等。

一段 PHP 程序被执行的时候会先被解析成 opcode 指令,然后在虚拟机中按顺序执行,由于 PHP 本身是用C语言开发的,所以其在执行的时候调用的都是C的函数。opcode 是 PHP 程序执行的最基本单位。

HashTable 是 Zend 的核心数据结构,实现了 PHP 里几乎所有的功能,支持 key->value 查询,添加删除的复杂度是 O(1),支持线性遍历和混合类型。

在 HashTable 中既有 key->value 形式的散列结构,也有双向链表模式,使得它能够非常方便地支持快速查找和线性遍历。

Zend 的散列结构是典型的 hash 表模型,通过链表的方式来解决冲突。

Zend 的 HashTable 是一个自增长的数据结构,当 hash 表数目满了之后,其本身会动态地以2倍的方式扩容并重新布置元素位置,初始大小均为 8。

另外,在进行 key->value 快速查找的时候,Zend 本身还做了一些优化,通过空间换时间的方式加快速度。比如在每个元素中都会用一个变量 nKeyLength 标识 key 的长度以做快速判定。

Zend HashTable通过一个链表结构实现了元素的线性遍历。理论上,做遍历使用单向链表就够了,使用双向链表的主要目的是为了快速删除链表元素,避免遍历。

PHP 是一门弱类型语言,本身不严格区分变量的类型。PHP 在声明变量的时候不需要指定类型。PHP在程序运行期间可能进行变量类型的隐式转换。和其他强类型语言一样,程序中也可以进行显式的类型转换。

Zval 是 Zend 中另一个非常重要的数据结构,用来标识并实现 PHP 变量。

Zval 主要由以下3部分组成:
Zval 用来保存一个变量的实际数据。因为要存储多种类型,所以 zval 是一个 union,也由此实现了弱类型。

引用计数在内存回收、字符串操作等地方使用得非常广泛。PHP 中的变量就是引用计数的典型应用。Zval 的引用计数通过成员变量 is_ref 和 ref_count 实现。通过引用计数,多个变量可以共享同一份数据,避免频繁复制带来的大量消耗。

在进行赋值操作时,Zend 将变量指向相同的 Zval,同时 ref_count++,在 unset 操作时,对应的 ref_count-1。只有 ref_count 为 0 时才会真正执行销毁操作。如果是引用赋值,Zend 就会修改 is_ref 为 1。

PHP 变量通过引用计数实现变量共享数据,当试图写入一个变量时,Zend 若发现该变量指向的 Zval 被多个变量共享,则为其复制一份 ref_count 为 1 的 Zval,并递减原 Zval 的 refcount,这个过程称为“Zval分离”。

可见,只有在有写操作发生时,Zend 才进行复制操作,因此也叫 copy-on-write(写时复制)。

对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。

PHP 7 的新特性

相较于以前的版本,PHP 7 在语言语法层面和底层架构层面都有一些改进。

在语法层面的改进主要是增加了一些新特性、移除了一些扩展、改变了错误异常处理等。

在底层结构方面,改变了存储各种变量的 Zval 和 Zend_String 结构体、优化了 Zend Array 的 HashTable、改进了函数的调用机制等。这些底层结构的改进大幅提升了 PHP 的执行效率,使得其执行速度比 PHP 5 高出一倍左右。

PHP 是一个弱类型的语言,不过在 PHP 7 中支持变量类型的定义,引入了一个开关指令 declare(strict_type=1);。

这个指令一旦开启,就会强制当前文件下的程序遵循严格的函数传参类型和返回类型。
要使用严格模式,一个 declare 声明指令必须放在文件的顶部。这意味着严格声明标量是基于文件可配的。这个指令不仅影响参数的类型声明,还影响函数的返回值声明。

PHP 7 中的新特性主要有以下几点:
  1. 标量类型声明。
  2. 函数返回值类型声明。
  3. 新增 null 合并运算符。
  4. 新增组合比较符。
  5. 支持通过 define() 定义常量数组。
  6. 新增支持匿名类。
  7. 支持 Unicode codepoint 转译语法。
  8. 更好的闭包支持。
  9. 为 unserialize() 提供过滤。
  10. 新增加 IntlChar 类。
  11. 支持 use 语句从同一 namespace 导入类、函数和常量。
  12. 新增整除函数 intdiv()。
  13. session_start() 支持接收数组参数。

除了以上列举的 13 点新特性之外,还有其他一些变更,读者可到 http://php.net/manual/zh/migration70.new-features.php 查看有关 PHP 7 新特性的详细变更和示例。

另外,在 PHP 7 中,很多致命错误以及可恢复的致命错误都被转换为异常来处理了。这些异常继承自 Error 类,此类实现了 Throwable 接口(所有异常都实现了这个基础接口)。

这也意味着,当发生错误的时候,以前代码中的一些错误处理的代码将无法被触发。因为在 PHP 7 版本中,已经使用抛出异常的错误处理机制了(如果代码中没有捕获 Error 异常,就会引发致命错误)。

在 2013 年的时候,惠新宸和 Dmitry(PHP语言内核开发者之一)就曾经在 PHP 5.5 的版本上做过一个 JIT(Just In Time,即时编译,一种软件优化技术)的尝试。

PHP 5.5 原来的执行流程是将 PHP 代码通过词法和语法分析编译成 opcode 字节码,然后 Zend 引擎读取这些 opcode 指令,逐条解析执行。

他们在 opcode 环节后又引入了类型推断(TypeInf),然后通过 JIT 生成 ByteCodes 再执行。

采用这种技术优化,PHP 的效率在实际项目中并没有取得明显的提升,于是他们重新设计了 PHP 的底层语言结构。

Zval 是存储 PHP 中变量的载体,是一个C语言实现的结构体(struct),PHP 5 的 Zval 在内存中占据 24 个字节,而在 PHP 7 中优化后的 Zval 只占 16 个字节,这样变量的存储变得非常简单和高效。

PHP 7 优化了数组的 HashTable 实现,PHP 5 的数组存储形式是一个支持双向链表的 HashTable,不仅支持通过数组的 key 来做 hash 映射访问元素,也能通过 foreach 以访问双向链表的方式遍历数组元素。

当我们通过 key 值访问一个元素内容的时候,有时需要3次的指针跳跃才能找对需要的内容。最重要的一点是,这些数组元素的存储是分散在各个不同的内存区域的,在 CPU 读取的时候,因为它们很可能不在同一级缓存中,导致 CPU 不得不到下级缓存甚至内存区域查找,从而引起 CPU 缓存命中下降,进而增加更多的耗时。

优化后的 Zend Array 最大的特点是整块的数组元素和hash映射表全部连接在一起,被分配在同一块内存中。如果是遍历一个整型的简单类型数组,效率会非常快,因为数组元素(Bucket)本身是连续分配在同一块内存里的,并且数组元素的 Zval 会把整型元素存储在内部,也不再有指针外链,全部数据都存储在当前内存区域内。当然,最重要的是它能够避免 CPU 缓存命中率下降。

另外,PHP 7 还改进了函数的调用机制,通过优化参数传递的环节减少了一些指令,提高执行效率。

推荐阅读