这一章通过错误与异常、生成器、引用、预定义变量、上下文选项和参数、协议、路由与伪静态、事件、信号和进程等十个方面深入理解PHP。
一、错误与异常 Error&Exception
每一门语言的错误与异常都有自己的思考和思想。
像C语言的数组下标越界,程序仍然能正常执行这个问题,被很多人所诟病,认为C语言应该将其列为错误,并中止程序。但其本质是C语言设计之初没有考虑到广大用户的平均水平。
Java在这一点上做出了改进,添加了数组下标越界这个异常。
在Golang的设计中,程序没有error,只有状态。所以也有很多人诟病,因为他们的程序中充满了 if(:err != nil){}这种代码。
PHP是怎样处理的呢?
在PHP7之前,一般都使用Exception。后来PHP社区认为Exception没有太多意义,所以在PHP7以后,大多数错误几乎都被视为Error。所以我们主要来看Error。
抛出异常
抛出异常的常见方式有三种:
-
die()
和exit()
-
throw
-
trigger_error
捕获异常
捕获异常的常见方式有两种:
-
try/catch
-
set_error_handler
和set_exception_handler
try/catch
由于Error类与Exception类没有继承关系,不可以用Error类去捕获Exception,反之亦然。
<?php
try {
throw new Error('hi error');
} catch (Error $e) {
echo 'error:', $e;
};
echo '<br/>';
try {
throw new Exception('hi exception');
} catch (Exception $e) {
echo 'exception:', $e;
}
set_error_handler()和set_exception_handler()会注册一个函数,来接管错误处理。
<?php
function wa_error(Error $e)
{
echo 'error:', $e;
}
set_error_handler('wa_error');
throw new Error('hi error');
自定义的错误处理函数,还可以接受4个参数。分别是错误类型、错误消息、错误文件、错误行号。和JavaScript中的onerror
极为相似。
function wa_error($error_type,$error_message,$error_file,$error_line)
{
echo $error_type,$error_message,$error_file,$error_line;
}
错误分类
常见错误主要有三类:
-
语法错误
-
运行时错误
-
逻辑错误(这类错误引擎并不会帮我们做出提示,因为程序运行本身没有出错。)
错误级别
错误的级别分四级:
级别 | 说明 | 触发原因 |
---|---|---|
deprecated | 最低级错误 | 使用了过时的函数或语法 |
notice | 通知级别错误 | 语法不当,如数组索引没加引号 |
warning | 警告级别错误 | 语法不当,如参数不匹配 |
fatal error | 致命错误,触发后程序无法继续执行 | 语法解析错误 |
prase error | 解析错误 | 最高级别的语法解析错误 |
PHP的错误级别多达十几种之多,但我们只需要关心以上五种就够了。
fatal error
前三种级别的错误都可以被捕捉,但fatal error无法捕捉。针对这种情况,需要使register_shutdown_function();它会再程序终止或者die时触发,给程序最后一点事件。概念类似于JavaScript DOM中的unload和onbeforeunload。
parse error
发生parse error时,连register_shutdown_function也无能为力。只能通过记录日志来查看错误。这时需要修改php.ini文件中的配置。
log_error=On
error_log=usr/log/php.log
除此之外还有很多其他选项,更多信息可以查阅官方文档。
二、生成器 generators
生成器的概念在很多语言中都有,比如ECMAScript6中就有。
PHP的生成器是在PHP5.5之后才引入进来的。
生成器的本质是提供了一种更容易的方法来实现简单的对象迭代。生成器往往会和迭代器 Iterator 一起出现。
什么是迭代器?在JavaScript中,凡是可以被循环的对象,都是迭代器的实例。这个概念拿到PHP中一样通用。也就是说,可以使用生成器来创建可循环(迭代)的对象,而不一定是数组。
<?php
function gen()
{
$i = 1;
for (; $i < 10; $i++) {
yield $i.'<br/>';// yield 关键字会产生一个值。下次再调用时,会继续在上次yield的地方运行。
}
}
foreach (gen() as $i) {
echo $i;
}
在PHP中,生成器最重要的意义就是性能会很好,节省大量内存。
凡是一个函数返回一个数组,而你又需要遍历这个数组时,都可以使用生成器来代替。
应用场景:在读取大文件时比较有用。
三、引用
PHP中引用就是不同的变量名访问相同的变量内容。这和JavaScript中的引用是一个概念,但JavaScript更加复杂一些。
两个变量同时指向一个内存地址。
<?php
$a =& $b;
注意:引用不是指针。
引用传递
通过在函数的参数前面加&符号。只允许三种方式。
-
变量
-
new
-
函数返回的引用
引用返回
下面的例子中,可以发现,这种方式和Vue.js中的数据双向绑定非常相似。
<?php
class foo
{
public $value = 42;
// getValue 函数所返回的对象的属性将被赋值,而不是拷贝,就和没有用引用语法一样。
public function &getValue()
{
return $this->value;
}
}
$obj = new foo;
$myValue = &$obj->getValue(); // $myValue is a reference to $obj->value, which is 42.
echo $myValue . '<br/>';
$obj->value = 2;
echo $myValue . '<br/>'; // prints the new value of $obj->value, i.e. 2.
取消引用
使用unset 函数。
<?php
$a = 1;
$b =& $a;
unset($a);
?>
这样会断开变量 $a 和 变量内容 1 的联系。完全不会影响 $b 或者 1.
四、预定义变量
最新版本的PHP中大约有如下这些预定于变量。
-
超全局变量 — 超全局变量是在全部作用域中始终可用的内置变量
-
$GLOBALS — 引用全局作用域中可用的全部变量
-
$_SERVER — 服务器和执行环境信息
-
$_GET — HTTP GET 变量
-
$_POST — HTTP POST 变量
-
$_FILES — HTTP 文件上传变量
-
$_REQUEST — HTTP Request 变量
-
$_SESSION — Session 变量
-
$_ENV — 环境变量
-
$_COOKIE — HTTP Cookies
-
$php_errormsg — 前一个错误信息
-
$HTTP_RAW_POST_DATA — 原生POST数据
-
$http_response_header — HTTP 响应头
-
$argc — 传递给脚本的参数数目
-
$argv — 传递给脚本的参数数组
其中最常用到的有$GLOBALS、$_SERVER、$FILES这几个。
五、上下文选项和参数
PHP 提供了多种上下文选项和参数,可用于所有的文件系统或数据流封装协议。
创建上下文
stream_context_create
设置上下文
stream_context_set_option
设置参数
stream_context_set_params
参数列表
-
Socket context options — Socket上下文选项列表
-
HTTP context options — HTTP上下文选项列表
-
FTP context options — FTP上下文选项列表
-
SSL context options — SSL上下文选项列表
-
CURL context options — CURL 上下文选项列表
-
Phar 上下文(context)选项 — Phar 上下文(context)选项列表
-
Context 参数 — Context 参数列表
六、协议
协议是每一门编程语言都必有的,它决定了语言能够连接什么。
PHP支持的协议类型:
-
file:// — 访问本地文件系统
-
http:// — 访问 HTTP(s) 网址
-
ftp:// — 访问 FTP(s) URLs
-
php:// — 访问各个输入/输出流(I/O streams)
-
zlib:// — 压缩流
-
data:// — 数据(RFC 2397)
-
glob:// — 查找匹配的文件路径模式
-
phar:// — PHP 归档
-
ssh2:// — Secure Shell 2
-
rar:// — RAR
-
ogg:// — 音频流
-
expect:// — 处理交互式的流
自定义协议类型
可以看到,PHP原生是没有对websocket协议和https协议进行支持的。但PHP提供了stream_wrapper_register函数,可以让用户自定义协议类型,PHP协议有一些是伪协议。什么是伪协议?写过JavaScript的同学应该看过javascript:viod(0)
这种代码吧,这就是伪协议,语言自己定义的。
相关配置
关于支持协议的相关配置,在php.ini文件中有对应的选项。
-
allow_url_fopen :on
默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。 -
allow_url_include:off
默认关闭,该选项为on便是允许 包含URL 对象文件等
allow_url_include依赖allow_url_fopen,如果不开启allow_url_fopen,allow_url_include就无法使用。
File协议
file协议是每一门服务端语言或建立在操作系统之上的语言都有的。file协议读取文件都是本地的,非常快。它不受allow_url_fopen与allow_url_include的影响。
语法:file:// [文件的绝对路径和文件名]
PHP协议
php协议用于访问输入流/输出流。
七、伪静态路由
这一节应该放在PHP扩展里面讲的,但为了凑十个小节,就拿到这里来了。
什么是伪静态路由?
伪静态就是让动态的URL地址看起来是静态的地址,伪静态是最终的目的,而不是技术。
举个例子:
在浏览器中输入
http://localhost/test/index1.html
http://localhost/test/index2.html
http://localhost/test/index3.html
都会转向 http://localhost/test/index.php 页面
并且依次转到
http://localhost/test/index1.html
http://localhost/test/index2.html
http://localhost/test/index3.html
但页面会显示不同的内容。
为什么需要伪静态?
因为很多地址都是动态的,会包含很多参数,这样地址栏的链接就会很长。
这样有什么问题呢?首先这很难看,其次对搜索引擎不友好。
有哪些实现技术?
-
web主机rewrite模块(URL重写,rewrite翻译成中文为重写,但很多人也常称伪静态)
-
pathinfo (主机支持的一种技术,index.php/path,再利用url重写可以隐藏入口)
-
程序的路由(可以美化,缩短url,变得更人性化,更有语义)
受篇幅限制,这里就不挨个细说了。仅讲一下apache服务器的配置。
Apache实现伪静态
共三步。
1.将配置文件中httpd.conf #LoadModule rewrite_module modules/mod_rewrite
前面的注释去掉。
2.在httpd.conf中添加
RewriteEngine On
\#RewriteCond %{ENV:SCRIPT_URL} (?:index|dispbbs)[-0-9]+.html
RewriteRule ^(.*?(?:index|dispbbs))-([-0-9]+).html 1.php?__is_apache_rewrite=1&__rewrite_arg=2
3.在<IfModule mod_rewrite.c>
和</IfModule>
之间添加如下配置。
RewriteMap tolowercase int:tolowerRewriteCond %{QUERY_STRING} (?:boardid|page|id|replyid|star|skin)=d+ [NC]RewriteRule ^(.*(?:index|dispbbs)).asp 1.php?{tolowercase:%{QUERY_STRING}}&__is_apache_rewrite=1
八、事件
什么是事件?
首先说一下Web服务端程序的原理,这和其他类型的计算机程序是不同的,有一些计算机程序执行完毕就会退出。但Web服务端程序大都会监听一个端口,并且卡在这里,使程序处于静止状态,等接收到http请求后,调动可用资源,执行相关任务,不停的重复此类行为。
我们可以把任何Web服务端程序都理解为两个状态。一个是“就绪”状态,一个是“执行”状态。
什么都不做的时候,就是“就绪”状态。执行任务的时候,就是“执行”状态。
那怎样让程序从“就绪”状态切换到“执行”状态呢?通过事件来处理的语言,就称为事件驱动型语言。最典型的就是JavaScript。
很可惜,PHP没有内置事件。所以很多框架自己实现了事件。
实现事件的原理并不复杂,一般使用观察者模式或者发布订阅模式来实现。发布订阅模式和观察者模式没有本质的区别,只是加入了消息队列。
这是一组对比图,可以看一下。
为了便于理解,我们可以自己写一个观察者模式的事件类。
<?php
interface Subject
{
public function fireEvent();
public function addObserver(Observer $observer);
public function removeObserver(Observer $observer);
}
interface Observer
{
public function subscribe(Subject $subject);
public function unsubscribe(Subject $subject);
public function update();
}
class EventSubject implements Subject
{
private $container = [];
public function fireEvent()
{
foreach ($this->container as $observer) {
echo var_dump($observer);
echo $observer->update()."<br/>";
}
}
public function addObserver(Observer $observer)
{
array_push($this->container, $observer);
}
public function removeObserver(Observer $observer)
{
$key = array_search($observer, $this->container);
array_splice($this->container, $key, 1);
}
}
class EventObserver implements Observer
{
public function subscribe(Subject $subject)
{
$subject->addObserver($this);
}
public function unsubscribe(Subject $subject)
{
$subject->removeObserver($this);
}
public function update()
{
echo '事件触发';
}
}
$eventSub = new EventSubject();
$eventOb1 = new EventObserver();
$eventOb2 = new EventObserver();
$eventOb3 = new EventObserver();
$eventOb1->subscribe($eventSub);
$eventOb2->subscribe($eventSub);
$eventOb3->subscribe($eventSub);
$eventSub->fireEvent();
观察者模式的本质是一种一对多的关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
九、信号
信号的本质:进程间通信。
信号是事件发生时对进程的通知机制,有时又称为软件中断。一个进程可以向另一个进程发送信号,比如子进程结束时都会向父进程发送一个SIGCHLD(17号信号)来通知父进程,所以有时信号也被当作一种进程间通信的机制。
PHP的 pcntl 扩展以及 posix 扩展为我们提供了若干操作信号的方法。
由于扩展安装出现一些小问题,这块没办法继续进行,明天补回来。
十、进程
首先明确一下进程的概念。
进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位。
进程号:就是PID,每个进程都有一个独一无二的进程号。
进程有什么用?处理一些耗时任务,一堆进程一起干,会很快。
PHP进程需要 pcntl 扩展支持。
进程的编程难度还是较高的,很容易出现问题。这里简单说几个概念。
孤儿进程:父进程死了,但子进程还活着。这些子进程会被一个进程号为1的init进程收养。
僵尸进程:一个进程通过fork创建子进程,子进程退出,父进程没有获取子进程状态,那么子进程的进程号还存在系统中。这样就会一直占用着进程号,如果有大量僵尸进程存在,把系统所有能用的进程号全部占用了,就会造成无法产生新进程的情况。
PHP进程的概念还是比较多的,更多的内容我也没来得及仔细研究,这里仅进行初步探索,较深入的知识以后有机会再细学。