(旧文)PHP语言介绍

php.net

简介

PHP来源于工程PHP/FI,由Rasmus Lerdorf创建于1995年,起初只是一套简单的Perl脚本,名字叫做“Personal Home Page Tools”,语法也和Perl很像,随着用户的增加,改进为用C语言实现。1997年,Andi Gutmans 和 Zeev Suraski 重写了代码,推出第三版,PHP/FI也演变成PHP(PHP: Hypertext Preprocessor)。注意,这是一个递归的缩写。

1999年,由两人改进的更具模块化的“Zend Engine”引入PHP,在结合了许多新功能后,2000年5月发布官方版PHP 4.0。如今广泛使用的5.x版本从2004年起发布。5.x版本支持完整的面向对象模型。目前的最新版本已经到了7.x版本(直接从稳定的5.6版跃迁)。

由于丰富的PHP主要用于服务端的脚本程序,就像其他的CGI程序,如收集表单,生成网页,发送/接收Cookie等。除此以外,PHP还用于命令行脚本,编写桌面应用程序。这两种开发可能会用到PHP的拓展库。由于解析器的存在,PHP的跨平台能力很好。

关于php的绝大多数内容都可以在php.net上找到,上面介绍的历史也是如此。本文的绝大多数内容更是如此。

安装和配置

在通常情况下,php用于服务器端脚本,安装配置较之Javascript复杂很多。在Unix环境下,假设服务器环境(如Apache, Nginx等)已经安装完毕,可以通过configure脚本安装配置。Windows环境下,通过MSI文件安装配置PHP和所有内置以及PECL拓展库。此外Mac OS X,云平台等安装各有不同,详见官方教程

配置文件(php.ini)在PHP启动时被读取,作为服务器模块版本的PHP,仅在服务器启动时读取1次,作为CGI和CLI版本,每次调用都会读取。用户亦可自定义自己的user.ini文件。PHP的有些指令可以在PHP脚本中用ini_set()设定,有些只能在php.inihttpd.conf中设定。这些是由指令的模式决定的,模式有4种:PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_ALL。具体见文档

PHP作为Apache模块运行时,还可以用php_value, php_flag, php_admin_value, php_admin_flag命令设置。

第一段代码

与C等语言通过代码输出HTML不同的是,PHP页面本身就是HTML,你也完全可以像通常建立HTML页面那样创建和编辑PHP页面,只不过其中嵌入了<?php?>包裹的PHP代码。与Javascript不同的是,PHP运行在服务端,用户无从得知脚本是如何运行的。

值得一提的是,除了上述的开始和结束标记,使用<script language ="php"></script>或者asp风格的短标记<?=, <%=也行(不建议)。在这一对标记之外的内容都会被PHP解析器忽略。可以在脚本中通过phpinfo()打印php的整体配置信息。

1
2
3
<?php
phpinfo();
?>

在html语句中嵌入php语句时,尽量做到将业务逻辑和展示语句隔离开对维护php工程有着极大的好处。

特性参考

PHP标识

如上文中提到,PHP通过<?php?>分隔php脚本,在php.ini激活short_open_tag配置后,支持使用短标记作为分隔符。如果文件内容为纯PHP代码,最好在文末删除结束标记,以免打印意料之外的空白。如下示例:

1
2
3
4
5
<?php
echo "Hello world";
// ... more code
echo "Last statement";
// stop here

PHP使用分号作为分隔符,支持C,C++,Perl风格的注释。即///**/#

类型 & 类型转换

PHP的原始数据类型有boolean,integer,float,string,array,object,resource,NULL。其中前4种为标量,第5,6中为复合类型。resouce表示资源,NULL表示无类型。PHP中float也称为double。在确保代码易读性上,还有mixed,number和callback几种伪类型。需要注意的是,PHP和Javascript一样,类型往往根据上下文确定。

boolean

和JavaScript类似。

只有TRUE或FALSE,除了false以外,还有下列假值:

  • 0
  • 0.0
  • ‘’
  • “0”
  • [],
  • {}(仅4.0)
  • NULL

其余均为真值(包含任何resource)。和Javascript类似,支持===全等。

integer

有十进制,十六进制,八进制,二进制表示。除十进制外,分别以0, 0x, 0b开头。5.0.5后最大值可以用常量PHP_INT_MAX设置。整数溢出时会被解释为float注意:八进制中传递非法数字后,后面数字会被忽略。类型转换时,可以使用intval()。在浮点数过大,分数强制转换和其他类型转换时,结果未定义。

float

又称为double和real,支持科学记数法。运算时精度有限,高精度要求下参考任意精度数学函数和gmp函数。在比较大小时需要谨慎,可以采用相减之差和最大容忍度比较的方法作折衷。常量NAN表示浮点计算中不可描述的值,为float类型,不等于任何其他变量,甚至自身。可以用is_nan()检查。

string

和JavaScript区别较大。

  • PHP字符串的字符占1个字节,因此不支持Unicode。字符串最长可达2GB
  • 表示字符串有4种方法,单引号,双引号,heredoc和nowdoc
    • 单引号下,只转义单引号和反斜线,其余字符均为plain text,支持多行;
    • 双引号下,对换行回车制表符等特殊字符进行转义,还会对变量解析($xxx)的形式(和Javascript相似)。
    • Heredoc结构里,在<<<符号后提供一个标识符然后换行,接下来是字符串本身,字符串后另起一行用前面定义的标识符作为结束标志。中间内容的处理方式同双引号。
1
2
3
4
5
6
<?php
$str = <<<EOD
Example of string
spanning multiple lines
using heredoc syntax.
EOD;

5.3.0以后,可以使用heredoc结构初始化静态变量和类的属性以及常量。nowdoc结构类似于单引号版的heredoc,但是跟在<<<之后的标识符要用单引号括起来,多用在不解析特殊字符的大段文本中。在双引号或heredoc结构中,变量会被解析,简单语法下,PHP解析器会去组合尽量多的标识形成一个合法的变量名。复杂语法下,$符号的外侧或里侧会紧贴{},来实现更复杂的变量表达式。

字符串中的字符可以用[]或者{}(不建议)访问。下标超出字符串长度时,会将多出的长度用空格填充。另外,字符串使用.连接。使用strval()转换变量为字符串,boolean会转成"1"""。integer和float作字面转换。**array总转换成"Array"**。object总转换成”Object”。NULL总转变成""

serialize()可以串行化大部分PHP值。字符串转为数值时,类似Javascript的parseInt()/parseFloat(),试图从头转换直到遇到不合法字符,支持科学记数法。区别在于PHP中失败时返回0而不是NAN

关于string的更多介绍,参加官方文档String一章。

array

1
2
3
4
5
6
7
8
9
10
11
<?php
$array = array(
"foo" => "bar",
"bar" => "foo",
);

// 自 PHP 5.4 起
$array = [
"foo" => "bar",
"bar" => "foo",
];

与Javascript区别较大,PHP中的数组也是个有序映射,描述了keys到values的映射。array使用array()初始化,在5.4版本后支持字面量定义。key可以是integer或string(integer时是数组,string时是键值对),value可以是任何类型。使用[]访问和修改数组元素,通过unset()删除某键值对(类似与Javascript的delete)。有趣的是,使用[]不指定键名时,则取当前最大整数索引值(曾经存在即可),新的键名在之上加1。可以使用array_values()重建索引。

转换为数组时,除object、NULL类型外,其余类型得到只有一个元素的数组。object类型转换时,单元为对象的属性,键名为成员变量名,还有其他特殊情况见文档数组部分。NULL会转换为一个空数组。

1
2
3
4
5
6
7
8
9
10
<?php
class A {
private $A; // This will become '\0A\0A'
}
class B extends A {
private $A; // This will become '\0B\0A'
public $AA; // This will become 'AA'
}

var_dump((array) new B());

object

对象,通过new来实例化一个类产生。转换为对象时,PHP会创建一个内置类stdClass的实例,可以通过new stdClass()创建一个空对象。php 7后,还有new class{}(object) []方法。

resource

用于保存到外部资源的一个引用,通过专门的函数建立和使用,由Zend引擎维护资源回收。

NULL

表示一个变量没有值。可细分为被赋值为NULL,尚未赋值和被unset()NULL不区分大小写

callback

类似Javascript中的function类型,一些函数如call_user_func()可以接收用户定义的回调函数作为参数。传递时,以string类型传递函数名。5.3.0后可以直接传递closure给回调参数。

其余伪类型多用于代码的说明注释中,如mixed表述多种不确定类型,void表述函数返回值无用或不接受任何参数等。

类型转换

使用var_dump()查看值和类型,gettype()查看类型,is_int/is_string/…判断类型,(type)settype()强制类型转换。PHP的强制转换和C非常相似。目前支持(int), (bool), (float), (string), (array), (object), (unset)(转换为NULL)。5.2版本后支持(binary)转换。

除了强制转换,PHP中会根据需要对变量自动转换,如加法。与Javascript的+不大不同,PHP会优先将操作数转为float,否则会将操作数解释为integer。数组的键名会优先转换为integer(仅十进制),再转换为string。下面就是一个有趣的例子:

1
2
3
4
5
6
<?php
$foo = "0"; // $foo is a string
$foo += 2; // $foo is an int now
$foo = $foo + 1.3; // $foo is a float now (3.3)
$foo = 5 + "10 Little Piggies"; // $foo is an integer (15)
$foo = 5 + "Small Pigs"; // $foo is an integer (5)

变量 & 常量

PHP变量以$符号开头,只能包含数字字母(这里说的字母包含ASCII字符)和下划线且不能以数字开头。变量区分大小写。$this是特殊变量不能赋值。可以在$前加&符号引用赋值,在改变原变量时,目标变量也会改动。isset()可以检查变量是否已被赋值。

1
2
3
4
5
6
7
8
<?php
$var = 'Bob';
$Var = 'Joe';
echo "$var, $Var"; // 输出 "Bob, Joe"

$4site = 'not yet'; // 非法变量名;以数字开头
$_4site = 'not yet'; // 合法变量名;以下划线开头
$i站点is = 'mansikka'; // 合法变量名;可以用中文

用户在为变量命名时,有几点要注意的。function, class, interface, 常量和函数外定义的变量会进入全局命名空间;建议在函数名中用_区分,类名中用驼峰或首字母大写的驼峰命名。注意:很多情况下,PHP会自动将变量名中的点转换成下划线。

可变变量

PHP中的变量名可以很方便地改变,而且可变变量可以用在数组或对象中,如下面的例子,。使用可变变量时,注意通过花括号给属性名清晰定界。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// Given these variables ...
$nameTypes = array("first", "last", "company");
$name_first = "John";
$name_last = "Doe";
$name_company = "PHP.net";

// Then this loop is ...
foreach($nameTypes as $type)
print ${"name_$type"} . "\n";

// ... equivalent to this print statement.
print "$name_first\n$name_last\n$name_company\n";

预定义变量

PHP提供许多预定义的变量。PHP中的许多预定义变量都是“超全局的”,这意味着它们在脚本的全部作用域都可见。这种类型在4.1版本中被引入,有$GLOBALS, $_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, $_REQUEST, $_ENV。它们在5.4版本后不能作为函数的输入参数。通过这些预设的超全局变量,PHP可以轻松地获取请求的各种参数。

除了上述超全局变量外,还有$php_errormsg, $HTTP_RAW_POST_DATA(使用php://input代替), $http_response_header(使用HTTP包装其时,该变量会被自动填充),$argc$argv分别代表传递给脚本的参数数目和参数数组(运行在命令行下时)。

作用域

变量作用域通常为文件作用域。函数内部的声明的变量被限制在函数作用域内。同时,和Javascript相同,PHP没有块级作用域。注意,PHP中定义全局变量需使用global关键字。在函数内部,变量优先视作局部变量。下面的脚本不会有任何输出,因为echo引用了一个局部变量$a,但是在函数作用域内它并没有被赋值。要想$a在函数作用域内可见,需要在引用前声明global

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$a = 1; /* global scope */

function Test() {
echo $a; /* reference to local scope variable */
}
Test();

function Test2() {
global $a;
echo $a;
}
Test2();

静态变量通过static声明,仅在局部作用域存在,程序离开作用域时内容不丢失。静态变量不能使用表达式初始化。在下面的例子中,函数仅在第一次调用时初始化$a变量,之后每次调用都会输出$a,并加一。

1
2
3
4
5
6
7
<?php
function test()
{
static $a = 0;
echo $a;
$a++;
}

常量

在类外,常量通过define(name. value)函数定义。在类内使用const定义常量(5.3.0后)。常量的命名规范同C。常量只能是标量。在访问常量值时,名字前不带$。常量名事先无法确定时,使用constant()获取常量。常量没有作用域的限制,可以在任何位置访问。

PHP定义了大量的魔术常量,都以两个下划线开头和结尾。有__LINE____FILE__, __DIR__, __FUNCTION__, __CLASS__, __TRAIT__, METHOD__, __NAMESPACE__。具体解释见官方文档

表达式 & 流程

PHP的表达式根据官方的定义表述,是任何有值的东西。表达式的组成类似于其他语言,从略。值得注意的有以下几点:

  • PHP的逻辑运算符同时有&&, ||以及and, xor, or两套,但是后一套的优先级最低
  • PHP提供@作为错误控制运算符,放置在表达式前可以忽略产生的任何错误信息。强烈不建议使用
  • 反引号``执行其中的shell命令,并将输出结果返回,等同于执行shell_exec()
  • +, ==, ===还可以用于数组间的运算,进行数组的连接,键值对相同的检测。
  • instanceof用于确定变量是否属于某个类的实例。用法如$a instanceof MyClass

算法流程上,PHP类似C风格。不同点有:

  • 提供在<?php>闭合标签内使用for endfor这种用法
  • foreach(array as $key => $value)便于遍历数组。(注意:在$value&可以在foreach循环中改变value的值)
  • break可以接受一个可选的数字决定跳出几层循环
  • continue接受一个可选的数字参数来决定跳过几重循环到循环结尾。默认值是1
  • declare设定一段代码的施行指令,目前只支持ticksencoding。前者控制执行计时的若干条命令后的操作,后者决定代码的运行编码。
  • requireinclude效果类似,用法同C,它们也有带后缀_once的操作符

函数

PHP的函数定义和其他语言类似,定义的函数都具有全局作用域。不同的是

  • PHP可以使用create_function(args, code)这样的函数定义函数(类似JS中的new Function()
  • 函数需要先定义后使用(这个只是与Javascript不同)
  • PHP可以定义有条件的函数,通过用if包裹和放在function定义内

和C/C++风格很像的是:

  • PHP函数参数接收的是一个复制,需要传递引用改变原值;
  • 支持默认参数,需放在最右;
  • 5.0之后支持对输入参数类型检查,到5.4为止支持class / array / callable类型,7.0以后支持标量类型。如果给出的值类型不对,那么将会产生一个错误
  • 7.0之后支持不对输入参数强制类型转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class C {}
class D extends C {}

// This doesn't extend C.
class E {}

function f(C $c) {
echo get_class($c)."\n";
}

f(new C);
f(new D);
f(new E);

和Js相似的一点时,5.6版之后支持使用...符号获取参数列表。

和可变变量一样,PHP中有可变函数,用法和可变变量一样。在调用对象的静态方法时,函数调用要优于静态属性,下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Foo
{
static $variable = 'static property';
static function Variable()
{
echo 'Method Variable called';
}
}

echo Foo::$variable; // This prints 'static property'. It does need a $variable in this scope.
$variable = "Variable";
Foo::$variable(); // This calls $foo->Variable() reading $variable in this scope.

5.3之后,PHP也支持匿名函数,并可以传递给一个变量储存。实际中,这种表达式会被转换为内置类Closure的对象实例。闭包可以从父作用域继承变量,但是此类变量需要用use结构传递进去,类似于function() use($a){}这样的形式。

类 & 对象

PHP承袭着面向对象语言对类和对象的处理。类以class开头,里面包含属性和方法等,可以包含自己的常量。通过new实例化,通过extends实现继承。子类使用parent::访问被覆盖的属性或方法,使用self::自身的静态属性和方法。5.5之后使用ClassName::class可以获取带有命名空间的完整类名。轻量级的类可以通过强制转换关联数组实现。

类中的静态属性通过::访问,非静态属性通过->访问。定义常量时使用const,常量的值必须是一个定值(5.6之后可以是数学运算结果)。PHP 5新增了关键字final,修饰方法或者类不可被继承。

PHP 5中,**__autoload()函数会在使用未定义的类时自动调用**,5.3.0之后通常使用spl_autoload_register()作为autoload的替代。__construct()__destruct()分别是构造和析构函数,5.3.3之前,在没有__construct()函数也没有父类时,会寻找命名空间中与类名同名的方法。

1
2
3
4
5
6
7
<?php
spl_autoload_register(function ($class_name) {
require_once $class_name . '.php';
});

$obj = new MyClass1();
$obj2 = new MyClass2();

trait & 匿名类

在访问控制,继承,抽象类,接口等方面PHP和传统的面向对象语言很像。在5.4.0后,PHP提供了trait作为类之间代码水平复用的特性(很像mixin)。在class定义中使用use来获取trait,类似interface,一个类可以插入多个trait,trait会覆盖基类方法而被当前类方法覆盖。在多个trait的同名方法发生冲突时,通过insteadofas来决定使用哪个,具体见trait文档。trait的功能使用依赖注入也可以完成,相关讨论见stackoverflow trait practivestrait vs interface。trait甚至还支持抽象成员和静态成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}

trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}

class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}

class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}

PHP7.0之后支持匿名类,用于创建一次性的简单对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// PHP 7 之前的代码
class Logger
{
public function log($msg)
{
echo $msg;
}
}

$util->setLogger(new Logger());

// 使用了 PHP 7+ 后的代码
$util->setLogger(new class {
public function log($msg)
{
echo $msg;
}
});

“重载”

PHP提供的重载(overload)语义和其他大部分OOP语言不同,指在调用当前环境下未定义或不可见的类属性或方法时调用重载方法。PHP借助魔术方法实现重载。读写不可访问属性时,__get()__set()分别被调用;对不可访问属性调用issetunset时,__isset()__unset()分别被调用。属性重载只能在对象中进行。调用不可访问的方法和静态方法时,__call()__callStatic()分别被调用,方法重载用法类似属性重载。重载的示例见文档。(不建议使用这个特性,这会影响ide补全和代码的可读性)。

PHP5提供foreach方法遍历对象,默认情况可见属性都会被遍历,可以让类实现Iterator接口从而自行决定如何处理遍历。实现IteratorAggregate接口可以代替实现所有的Iterator方法,IteratorAggregate只需实现IteratorAggregate::getIterator()方法即可。

魔术方法

PHP将所有__开头的类方法保留为魔术方法__sleep()方法在serialize()函数前调用,应返回一个包含对象中所有应被序列化的变量名称的数组,相对的__wakeup()在反序列化函数前调用。__toString()在把一个类视作字符串时怎样回应时调用。__invoke()在把一个类视作函数调用时调用。更多方法见魔术方法页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
class Connection
{
protected $link;
private $server, $username, $password, $db;

public function __construct($server, $username, $password, $db)
{
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}

private function connect()
{
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}

public function __sleep()
{
return array('server', 'username', 'password', 'db');
}

public function __wakeup()
{
$this->connect();
}
}

PHP使用clone创造对象的浅复制(即只创造属性的引用),魔术方法__clone()在clone完成后调用。PHP 5中的对象甚至可以相互比较,使用==判断属性和属性值是否一致,===判断变量是否是同一个实例。

自5.3.0起,PHP增加了static::关键字和后期静态绑定的功能,用于在继承范围内引用静态调用的类。静态环境下绑定静态方法可以让子类在自己的环境下(自己的this)调用继承自基类的方法。这种方式绑定非静态方法时,会出现不同结果,尽量避免使用。

对象通过serialize()unserialize()来序列化和反序列化一个对象,对象的方法和静态成员不会保留。在解序列的文件域内需要包含类的定义

预定义接口

PHP预定义了许多接口。Traversal接口监测一个类是否可以使用foreach进行遍历(仅供引擎使用)。Iterator接口用来实现对象的foreach迭代,有rewind, current, key, next, valid等成员方法。除此以外还有聚合迭代,数组式访问,序列化,生成器接口等接口和Closure类。这里从略。

命名空间

命名空间是PHP一个比较有特点的特性。在PHP中用命名空间解决类库和用户代码名字冲突的问题。实际上命名空间所做的事情就是代码模块化,正如Java的packages和Javascript里CommonJS规范一样。命名空间的命名方法类似变量,不允许使用PHP或php开头的命名空间。下面是一个命名空间的范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace my\name; 

class MyClass {}
function myfunction() {}
const MYCONST = 1;

$a = new MyClass;
$c = new \my\name\MyClass;

$a = strlen('hi');

$d = namespace\MYCONST;

$d = __NAMESPACE__ . '\MYCONST';
echo constant($d);

除了declare语句以外,namespace的定义需在文件的最前面。PHP与其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。在命名空间中使用define定义常量时,需要带上__NAMESPACE__,否则意味着定义在全局空间下。

PHP中的命名空间和文件目录很像,也支持层级化的定义方法,即定义子命名空间,父子间通过反斜线\隔开。可以在单文件内定义多个namespace(不提倡),建议namespace间通过大括号隔离开。PHP命名空间可以和文件系统进行类比,类名非限定时,会在当前空间寻找,以\开头时从全局空间寻找(相对目录),否则从当前空间起向下寻找(绝对目录)。下面是一个使用了三种方法的样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
namespace Foo\Bar;
include 'file1.php';

const FOO = 2;
function foo() {}
class foo {
static function staticmethod() {}
}

/* 非限定名称 */
foo(); // 解析为 Foo\Bar\foo
foo::staticmethod(); // 解析为类 Foo\Bar\foo的静态方法static method
echo FOO; // 解析为常量 Foo\Bar\FOO

/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo, 以及类的方法 staticmethod
echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO

/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO

命名空间的装载和名称的解析是在编译期完成的。命名空间有三种定义方法:

  • 非限定名称:名称中不包含命名空间分割符,即\,如Foo
  • 限定名称:名称中包含命名空间分割符,如Foo\Bar
  • 完全限定名称:名称中包含命名空间分割符,且以\开始的标识符,如\Foo\Bar

PHP支持使用namespace关键字或__NAMESPACE__魔术常量获取当前所在命名空间。所有支持命名空间的PHP版本支持三种别名或导入方式:为类名称使用别名为接口使用别名为命名空间名称使用别名。这么做类似于在操作系统中创建符号连接。别名通过操作符use as实现。注意:导入命名空间后文件内的类名,接口名等会收到导入的影响。在一个命名空间中,当PHP遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP会退而使用全局空间中的函数或常量。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称。

错误和异常

PHP的错误类型有很多,可以见类型列表。PHP对错误的汇报方式由php.ini中的error_reporting命令控制,可以在运行时通过error_reporting()函数动态修改。在开发环境,建议将级别设置到E_ALL,同时在脚本的开头设置级别。php.ini中的display_errors指令控制是否将错误显示在脚本输出中,建议在生产环境中关闭。log_errors指令控制错误记录。

PHP 5中异常可以被抛出,由try/catch语句块获取。catch获得的是一个Exception类的实例。类似Java,可以在catch后加上finally语句块。Exception是一个类,有getMessagegetTraceAsString等方法可以使用和拓展,详见介绍。PHP 7中,大多数错误都被作为Error异常抛出,可以被第一个匹配的try/catch语句块捕获,否则交给PHP相应的异常处理函数处理,如果尚未通过set_exception_handler()注册童永刚异常处理函数,则会报告一个Fatal Error。注意:捕获错误或异常时,若在自定义命名空间下,Exception需要用完全限定方式书写。

7.0以后的版本中,ErrorException同属于Throwable类型,这一点与5.x版本不同。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//To catch both exceptions and errors in PHP 5.x and 7, add a catch block for Exception AFTER catching Throwable first.
//Once PHP 5.x support is no longer needed, the block catching Exception can be removed.
try
{
// Code that may throw an Exception or Error.
}
catch (Throwable $t)
{
// Executed only in PHP 7, will not match in PHP 5
}
catch (Exception $e)
{
// Executed only in PHP 5, will not be reached in PHP 7
}

生成器(generator)

PHP中的生成器的概念与Java等高级语言中生成器的概念无二。生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成器可以yield生成许多它所需要的值。当一个生成器被调用的时候,它返回一个可以被遍历的对象。PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态。生成器不可以返回值。return语句只会终止生成器继续执行。

yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。可以使用yield返回键值对,引用或NULL等。在PHP 7以后,使用yield from可以从实现了Iterator接口的对象或使用yield的函数中yield值。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
function count_to_ten() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from seven_eight();
return yield from nine_ten();
}

function seven_eight() {
yield 7;
yield from eight();
}

function eight() {
yield 8;
}

function nine_ten() {
yield 9;
return 10;
}

$gen = count_to_ten();
foreach ($gen as $num) {
echo "$num ";
}
echo $gen->getReturn();

对比生成器和实现Iterator接口的类来看,生成器的代码可读性更高,代码量也更少,缺憾在于不能多次迭代和回退,除非重建或使用clone。

引用

PHP的引用意味着不同的名字访问同一个变量内容,通过在变量前加上&使用。在对一个未定义的变量进行引用参数传递或引用返回时,会自动创建该变量。不使用&符号时,意味着生成一个拷贝。

在进行引用传递时,只能传递变量,New语句和函数中返回的引用;引用返回时,需要在函数名前加上&符号,同时接受返回值的变量也需写为接收引用的形式。
通过unset销毁引用,销毁引用的同时不会销毁原变量(类似于删除符号链接)。global $var实际上就是创建了到$GLOBALS[]的引用。$this也是同理。

支持的协议

PHP带有内置URL风格的封装协议,可用于类似fopen()copy()file_exists()filesize()的文件系统函数,如file, http, ftp, php, zlib, data等。其中php://提供的是输入输出流和错误描述符的访问能力。创建数据流前,可以通过stream_context_create()创建上下文选项,定义数据流的选项。

函数参考

PHP本身提供了海量的函数。且都可以全局访问到。

内核

内核部分的函数,不能通过编译选项去除。PHP这部分的函数和介绍相当多,这里只撷选了常用的部分。

数组

这部分的函数主要用来进行和数组相关的操作,由于PHP中的数组包括了键值对这样类似对象的功能,函数的数量很多,甚至有些冗余。

  • array_chunk 将数组分割为多个,单元数目由size决定。返回一个多维数组。
  • array_merge 将多个数组的单元合并在一起,字符串键名相同时,后面的值会覆盖前一个。类似的还有array_merge_recursive。
  • array_count_values 统计数组中所有的值出现的次数,返回一个关联数组
  • array_diff 计算数组的差集,返回在array1但不在array2的元素
  • array_intersect 计算数组的交集,返回一个在array1中出现同时也在其他所有参数数组中出现的值。在差和交的名称前加上u的函数可以自己指定比较方法。
  • array_fill 用给定的值填充数组的num个条目,start_index为返回数组的第一个索引值。array_fill_keys函数可以填充键值对。array_pad 用值将数组填充到指定长度。键从第一个整型数开始,否则从0开始。
  • array_flip 返回一个交换键和值的数组。不合法的值将不会反转。类似的array_reverse返回一个单元顺序相反的数组。
  • array_combine(array $keys , array $values)返回一个由keys数组作键,values数组作值的新数组,两个数组长度不一样时抛出异常。array_keys(array $array [, mixed $search_value [, bool $strict= false ]])返回所有值为search_value的键名,strict表示是否进行严格比较。类似地,array_values(array $input)返回一个由所有值组成的数组,并建立起数字索引。
  • array_multisort用来一次多多个数组排序,输入数组被当作一个数据表的若干列来排序。常用在对数据库数据的排序。返回值为bool类型。
  • array_pusharray_pop分别在array的末尾弹出或压入一个元素。
  • array_shiftarray_uinshift完成类似于上面的功能,不过是在数组开头。
  • array_product()array_sum()分别返回数组的乘积和总和。
  • array_filter用回调函数过滤数组单元。没有回调函数时将删除input中等值于FALSE的条目。
  • array_map返回一个arr1所有单元经过callback作用后的单元。callback 接受的参数数目应该和传递给 array_map() 函数的数组数目一致。
  • array_walk使用用户自定义的函数对数组每个函数做回调处理。
  • array_reduce根据回调将array简化为一个值。function变量可以读取result和item。
  • array_replace(array $array1 , array $array2 [, array $... ])将前面的数组的键值对覆盖为后面的键值对。多维数组下有recursive版本。
  • array_key_exists检查键名是否存在于数组中。array_search(mixed $needle , array $haystack [, bool $strict = false ])在数组中搜索给定值。
  • array_slice根据offset和length从数组中取出一段。
  • array_splice把input数组中由offset和length指定的单元去掉,如果提供了replacement参数,则用其中的单元取代。
  • array_unique用于移除数组中重复的值

除了这些,还有is_array()explode()split()等不以array开头的函数和数组相关,大多用来进行一些简单的操作,列表见数组参考。和数组排序相关的函数也有很多,它们在排序依据,是否稳定等方面各不相同,更多内容参考对数组进行排序。

字符串

和字符串相关的函数也很多,但只有以str开头的是严格意义上的字符串函数。下面列举了部分:

  • addslashes转义字符串中的单引号,双引号,反斜线和NUL
  • chr返回ASCII码对应的字符,ord()是其互补函数
  • chunk_split($body[,int $chunklen = 76 [,string $end = "\r\n" ]] )拆分$body$chunklen的小块,每块后用$end结尾
  • crypt返回一个单向字符串散列,md5计算字符串的MD5散列值,sha1计算sha1散列值
  • echo 输出一组字符串
  • explode使用一个字符串分割另一个字符串;类似地,implode将一个一维数组的值转为字符串。又写作join
  • htmlentites转义所有的特殊字符为HTML实体;html_entity_decode()实现相反的步骤。
  • htmlspecialchars()htmlspecialchars_decode()完成的功能和上面相似,但是转义的字符只有&"'<>
  • lcfirst将首字母小写,ucfirst将首字母大写,ucwords将每个单词的首字母大写
  • ltrimrtrimtrim删除字符串首部,尾端和两端的空白。
  • str_getcsv解析csv字符串为一个数组
  • str_pad使用另一个字符串填充字符串到指定长度
  • str_repeat 重复一个字符串
  • str_replace 字符串替换,preg_replace的特殊情况
  • str_shuffle随机打乱一个字符串
  • str_split将一个字符串转换为数组
  • strstr查找字符串的第一次出现。stristr()则不区分大小写地查找
  • strcmp二进制安全字符串比较大小,strncmp类似,不过允许指定比较的长度,strnatcmp以自然顺序比较字符串
  • strlen获取字符串长度
  • strpos查找字符串初次出现位置,strrpos查找最后一次出现,strripos不区分大小写查找最后一次出现
  • strrev反转字符串
  • strip_tags去除str中的空字符,HTML标记和PHP标记,和fgetss()机制一样
  • strtoupper将字符串转化为大写,strtolower将字符串转换为小写
  • strtr()翻译、转换指定字符
  • substr返回字符串的子串
  • substr_count返回子字符串在字符串中出现的次数。

变量

  • boolvar()转换变量为bool类型
  • empty()判断变量是否为空
  • isset()检测变量是否已设置
  • intval()获取变量整数值,floatval()获取变量浮点数值,strval()获取变量的字符串表示
  • get_resource_type()获取资源类型
  • gettype()获取变量类型,settype($var, string $type)设置变量类型
  • is_array, is_bool, is_callable, is_float, is_int, is_null, is_numeric, is_object, is_resource, is_scalar, is_string用来检测各种类型。
  • print_r()var_dump()打印变量的相关信息,var_export()以合法PHP代码的形式返回变量的字符串表示
  • serialize()序列化一个变量,unserialize()反序列化一个变量
  • unset()销毁指定的变量

类和对象

  • spl_autoload_register()尝试在类名未定义时启动类的自动加载
  • class_alias为一个类创建别名。
  • class_exists检查指定的类是否定义
  • get_class()返回对象实例所属类的名字。类似地还有get_class_varsget_class_methods函数。
  • get_declare_classesget_declare_interfaces以及get_declare_traits获取脚本中已定义的类、接口、trait数组。
  • method_exists( mixed $object , string $method_name )检查类方法是否存在于指定object中,类似地还有property_exsitsinterface_existstrait_exist

日期和时间

PHP中的时间以64为数字存储。使用时需要配置好php.ini中时区等信息。DateTimeDateTimeZoneDateInterval等对象便于进行相关的操作。PHP同时提供了OOP风格和过程化风格两种方式使用函数。其中DateTimeDateTimeImmutable都继承自DateTimeInterface接口,有着diffformatgetTimestampgetTimezone等方法。

DateTime中的部分方法如下:

  • add(DateInterval $interval)在当前时间上加上一个时间段
  • sub(DateInterval $interval)在当前时间上减去一个时间段。
  • __construct(),创建一个对象,过程化风格: date_create()
  • createFromFormat创建一种时间格式format的写法格式见参考
  • modify修改当前时间,modify为合法的时间格式。
  • setDate(int $year, int $month, int $day )设置日期
  • setTime(int $hour ,int $minute [,int $second = 0 ])设置时间。
  • setTimestamp()设置时间戳。

上述方法都有对应的过程化风格的对应函数。

DatePeriod和DateTimeZone等的介绍从略。除了以上的对象方法过程化的函数外,还有以下一些常用方法:

  • date($format[, $timestamp),格式化一个本地时间
  • getdate(),获得日期时间信息,localtime功能类似,返回一个数组。
  • mktime获得一个日期的时间戳,默认为当前。类似的还有timemicrotime,返回一个时间戳类型
  • strtotime将英文文本的日期时间解析为Unix时间戳
  • 有意思的是date_sunsetdate_sunrise可以获取指定时间戳的日出日落时间

文件系统

  • basename()返回路径的文件名部分;dirname()返回路径中的目录部分,realpath()返回规范的绝对路径名
  • chgrp()改变文件所属组,类似的还有chmodchown
  • copy用于拷贝文件,rename用于移动和重命名文件。注意,这里没有delete函数。unlink用于删除文件
  • link ()建立一个硬连接,linkinfo, lstat给出连接信息。symlink创建一个符号连接。
  • mkdirrmdir用来创建和删除文件夹
  • file把整个文件都入到一个数组中,一行一个元素,可以使用URL作为文件名。file_exists检查文件或目录是否已存在。tmpfile则会建立一个关闭后自动删除的临时文件。
  • file_get_contents将文件读入到字符串中,可以使用stream_context_create创建上下文进行更细致的操作。
  • file_put_contents()写文件,和依次调用fopen(), fwrite(), fclose()效果一样。
  • fileatime, filectime, filemtime, filegroup, fileowner, fileperms, filesize, filetype, stat等和字面意义一样获取文件的各方面信息。它们接收文件路径作为参数。
  • is_dir, is_executable, is_file, is_link, is_readable, is_uploaded_file, is_writable检查文件各种属性
  • fopen打开一个文件,返回一个resource句柄,可以交给fread, fwrite, fscanf, fclose等函数做读写操作。
  • fgets从当前指针处读取一行,fgetc读取一个字符,fstat返回文件信息,ftruncate将文件阶段到给定长度。
  • glob()寻找和pattern匹配的文件路径

Directory类通过dir()创建。Directory实例有closereadrewind三种方法。分别用来释放句柄,读取条目和倒回开头。初次以外还有下面这些常用的目录相关函数。

  • chdir(string $directory)用来改变当前目录,
  • getcwd取得当前工作目录
  • scandir()返回一个包含目录中所有文件和目录的数组
  • closedir()关闭通过opendir()打开的目录流。
  • readdir()返回目录中下一个文件的文件名。文件名以在文件系统中的排序返回。

错误处理

下面这些函数允许你定义自己的错误处理规则,以及修改错误记录的方式:

  • debug_backtrace()产生一条PHP的回溯跟踪,返回数组类型;debug_print_backtrace()则将回溯打印出来。
  • error_get_last()获取最后一个发生错误的信息,error_clear_last()清除最后一个错误信息
  • error_log()发送错误信息到web服务器的错误日志或是一个文件里。
  • error_reporting()设置应该报告的PHP错误级别。
  • set_error_handler, set_exception_handler, restore_error_handler, restore_exception_handler分别是设置和重置错误以及异常处理的函数
  • trigger_error()触发一个用户级别的错误条件,在运行出现异常时,需要产生一个特定响应时很有用。

session

在会话支持下,每个访问网站的用户都有一个唯一的id标识,这个标识可以存储在cookie中,也可以通过URL传递。当一个访问者访问网站时,PHP将自动检查(如果session.auto_start被设置为1)或者在你要求下检查(明确通过session_start()或者隐式通 session_register()) 当前会话 id 是否是先前发送的请求创建. 如果是这种情况,那么先前保存的环境将被重建。

安全方面需要注意以下几点:

  • session.cookie_lifetime=0, 即浏览器不持久化存储cookie数据
  • session.use_cookies=On 并且session.use_only_cookies=On,即通过HTTP cookie实现会话ID管理
  • session.use_strict_mode=On,即禁止使用未初始化会话id的会话,从而防止Javascript进行会话ID的注入
  • session.cookie_httponly=On,禁止Javascript访问会话ID
  • session.cookie_secure=On,仅在HTTPS协议下访问session ID,用在仅支持HTTPS的站点
  • session.hash_function="sha256"。 高强度的散列函数可以产生高强度的会话ID

其他注意事项可以在PHP的会话与安全章节找到,根据实际需要选择。下面是一些session函数的使用:

  • session_destroy(), 销毁一个会话里的全部数据,但不会重置相关全局变量也不会重置cookie,再次使用时需要重新调用session_start()函数。为彻底删除session,需要调用setcookie()清除cookie中的session ID。
  • session_cache_expire()设置或读取当前缓存到期时间(这个只和浏览器页面刷新缓存有关)
  • session_id()获取/设置当前会话ID,PHP仅允许会话ID包括a-z A-Z 0-9 ,(逗号) -(减号).如果不是用cookie来存储session ID,session ID通常附在SID常量中,放在URL里。
  • session_regenerate_id()在不修改当前session数据的前提下使用新的ID替换原有会话ID。如果启用了session.use_trans_sid选项,那么必须在调用session_regenerate_id()函数之后开始进行输出工作,否则会导致使用原有的会话 ID
  • session_start()创建新会话或者重用现有会话。 如果通过GET或者POST方式,或者使cookie提交了会话ID,则会重用现有会话。
  • session_status()返回当前会话状态
  • session_write_close()写入session数据,然后关闭会话
  • session_name()设置或返回当前回话名称,名称应短小易懂,且不能由纯数字组成,如website_id
  • session_save_path()读取/设置当前会话的保存路径
  • session_unset()释放当前会话注册的所有会话变量

进程控制

这部分函数提供执行系统本身命令的能力。注意,以加锁方式打开的文件,必须在执行后台程序前关闭

  • escapeshellarg(string $arg)escapeshellcmd(string $command)对参数和命令元字符转义,保证安全。
  • exec()passthru()都能执行一个外部程序,区别是前者返回结果的最后一行,后者返回未经处理的全部输出数据。
  • shell_exec()在shell环境下执行命令,以字符串的形式返回完整的字符串。
  • system(string $command[, int &$return_var ] )执行 command 参数所指定的命令,并且输出执行结果。

函数调用

  • call_user_func(callable $callback [, mixed $parameter [, mixed $... ]])把第一个参数作为回掉函数调用。在参数很多时,建议使用$callback(…values)的形式传入数组。类似的还有forward_static_callforward_static_call_array用来调用静态方法。
  • func_get_arg(int $arg_num)func_get_arg()返回自定义函数的参数和参数列表,用在函数体内。
  • function_exists()判断函数是否定义
  • register_shutdown_function, register_tick_function用来注册exit之后和每个tick后执行的函数

Hash

这部分函数自5.1.2版本后成为核心的一部分。

  • hash()根据指定的哈希算法生成哈希值。类似的还有hash_file
  • hash_hmac()使用HMAC方法生成带有密钥的哈希值,类似的还有hash_hmac_file
  • hash_init()初始化一个哈希运算上下文,返回resource类型
  • hash_update(resource $context , string $data)向活跃的哈希运算上下文中填充数据。细化的,还有hash_update_filehash_update_stream两个函数。
  • hash_final()结束哈希上下文,返回摘要内容。
  • hash_copy()返回一个哈希运算上下文副本。

PHP自身

这些函数允许你获得许多关于PHP本身的参数。

  • assert()检查一个断言是否为FALSE,并在失败的时候调用assert_options()中指定的回调函数
  • dl()运行时加载一个PHP扩展
  • get_cfg_var()获取PHP配置选项的值
  • get_current_user()获取当前PHP脚本所有者名称
  • get_included_files()返回被include和require文件名的 array
  • ini_get()获取一个配置选项的值
  • ini_set()设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复
  • ini_restore()恢复指定的配置选项到它的原始值
  • memory_get_usage()返回当前分配给你的 PHP 脚本的内存量,单位是字节(byte)
  • php_sapi_name()返回web服务器和PHP之间的接口类型
  • php_uname()返回运行PHP的系统的有关信息
  • phpinfo([int $what = INFO_ALL])输出关于 PHP 配置的信息。可以通过what筛选输出内容。
  • phpversion()获取当前的PHP版本
  • version_compare()对比两个「PHP 规范化」的版本数字字符串

数学

这部分函数处理integer和float范围内的计算。预定义常量包括M_PI, M_E, M_LOG2E, M_LN2, M_PI_2, M_1_PI, M_SQRT2, M_SQRT3, INF等诸多数学常量。函数名和其他语言类似。

  • 三角函数相关:sin, cos, tan, asin, acos, atan计算单位为弧度
  • 双曲函数相关:sinh, cosh, tanh, asinh, acosh, atanh, atan2
  • 对数相关:log, log10, log1p
  • 指数相关:pow, exp, expm
  • 近似相关:round, floor, ceil
  • 随机数相关: rand(int $min , int $max), mt_rand(用法同rand,性能更好), srandmt_srand(现已不需要使用)
  • 进制转换相关:bindec, octdec, hexdec转为十进制数(读取字符串,输出数字),相对应还有decbin, decbin, dechexbase_convert()可以做任意进制转换
  • 角度相关:deg2rad角度转弧度
  • 运算相关:intdiv返回商的整数部分,fmod返回浮点数余数。abs计算绝对值,sqrt计算开根号
  • 判断相关:is_finite, is_infinite, is_nan,
  • 其他:max, min(可以输入数组), pi, hypot(根据直角边计算三角形斜边长)

输出控制

PHP脚本有输出时,输出控制函数可以用这些来控制输出。如通过ob_start()将下文的输出放在缓冲区直到调用ob_end_flush()。通常配合header()使用,在真正返回数据前写入header和cookie。

从略。

杂项

  • constant()返回一个常量的值
  • define()定义一个常量
  • exit()输出一个消息并且退出当前脚本,dieexit的同名函数。
  • highlight_file()语法高亮一个文件
  • highlight_string()语法高亮一个字符串,使用方法同上。
  • sleep()延迟指定秒数执行。类似的还有usleep以指定微秒数暂缓执行,time_sleep_until使脚本睡眠到指定时间
  • uniqid()返回一个基于当前微秒级时间的带前缀的唯一ID。

绑定拓展库

下面的拓展库绑定在PHP发行包中。较之内核部分的函数,更偏向为解决某类问题而设计。这里也只摘选部分常用的介绍。

Ctype

用来检测 在当前的区域设定下,一个字符或者字符串 是否仅包含指定类型的字符。根据官方描述,“如果可以满足需求,请优先考虑使用 ctype 函数, 而不是正则表达式或者对应的 “str_*” 和 “is_*” 函数。 因为 ctype 使用的是原生 C 库,所以会有明显的性能优势”。在4.2.0版本后,这些函数是默认启动的。

  • ctype_alpha()纯字符检测
  • ctype_upper()大写字母检测
  • ctype_lower()小写字母检测
  • ctype_digit()纯数字检测
  • ctype_alnum()检查字符串内的字符否全部为字母或数字
  • ctype_cntrl()控制字符检测
  • ctype_print()字符是否都可以打印
  • ctype_graph()字符输出是否都是可见的
  • ctype_punct()字符是否都可打印却不是字母数组和空白
  • ctype_space()空白字符检测
  • ctype_xdigit()十六进制字符串检测

正则表达式

在PHP5.3版本后,原来的POSIX Regex不再推荐使用。兼容Perl的正则表达式库PCRE仍可以使用,且默认开启。这里仅介绍PCRE相关函数,它们均以preg开头。

  • preg_match()返回pattern在subject中的匹配次数
  • preg_match_all()搜索subject中所有匹配pattern给定正则表达式的匹配结果并且将它们以指定顺序输出到matches结果中.
  • preg_replace()执行一个正则表达式的搜索和替换,当$pattern和$replacement都是数组时,会进行相对应位置的替换。
  • preg_grep(string $pattern , array $input [, int $flags = 0 ])返回给定数组input中与模式pattern 匹配的元素组成的数组.
  • preg_split()通过正则表达式分割字符串,返回一个数组。

JSON

自5.2.0起,JSON拓展默认内置并编译进PHP。

  • json_encode()JSON编码一个变量。
  • json_decode()解码一个JSON格式的字符串
  • json_last_error()返回JSON编码时最后的错误

多字节字符串

在汉语中,每个字符通常占用2个字节,在使用string的相关函数时,可能会出现意外问题。多字节字符串即为了解决此问题。这不是一个默认扩展,需要在configure选项中显式激活。详见安装。另外,mbstring支持“函数重载”,即使用mb_xxx替代原有的字符串函数。

  • mb_detect_encoding()检测字符的编码
  • mb_ereg_xxx打头的与preg_xxx同名的函数为正则匹配多字节版
  • mb_strlen()获取字符串长度。
  • mb_split()使用正则表达式分割多字节字符串
  • mb_substr()执行一个多字节安全的substr()操作
  • mb_strpos()查找字符串在另一个字符串中首次出现的位置;类似地,mb_strrpos查找最后出现的位置。
  • mb_strstr ()查找字符串在另一个字符串里的首次出现;类似地,mb_strrchr()查找指定字符在另一个字符串中最后一次的出现

BCMath

这部分函数进行任意大小和精度的数字的二进制计算。自4.0.4后随PHP一起发布。Windows版本下是默认支持的。

  • string bcadd(string $left_operand , string $right_operand [, int $scale ] )加法。scale用来决定小数点位数,输入输出均未string类型,下同。
  • bcsub()减法
  • int bccomp()比较
  • bcmul()乘法
  • bcdiv()除法
  • bcmod()取模
  • bcpow()乘方
  • bcsqrt()二次方根
  • bcscale()设置所有bc数学函数的默认小数点位数

图像处理

PHP可以处理各种格式的图像,并把它们输出到浏览器。这需要在编译时指定GD库(除了getimagesize()函数)。GD库不仅能处理图像,还能对字体进行处理。使用PHP可以动态修改图像文件,或为图像添加水印信息,甚至创建一个图像。

下面是和图像信息相关的函数:

  • gd_info()获取当前安装的GD库信息
  • getimagesize()获取图像大小,返回数组类型,按顺序分别是宽度,高度,类型,描述宽高的字符串。getimagesizefromstring函数则通过打开的图片信息(字符串格式)中读取图像尺寸信息
  • image_type_to_extension()获取图像类型的文件后缀
  • imageistruecolor(resource $image)检查图像是否为真彩色
  • imagesx()返回image所代表的图像宽度;imagesy()返回所代表的图像高度
  • imagetypes()返回PHP支持的图像类型,int类型。

剩下还有众多以image开头的和创建、输出、删除图像,画图、编辑图片、设置颜色、设置字体相关的函数,见参考

Exif

通过Exif拓展,可以操作图像元数据。必须使用--enable-exif选项编译PHP,Windows用户还需要启用mbstring扩展。

  • exif_imagetype()读取一个图像的第一个字节并检查其签名
  • exif_read_data()函数从JPEG或TIFF图像文件中读取EXIF头信息
  • exif_thumbnail()读取TIFF或JPEG图像中的嵌入缩略图。如果图像不包含缩略图则返回FALSE

Socket

Socket拓展基于流行的BSD sockets,实现了和socket通讯功能的底层接口。在编译PHP时必须在配置中添加—enable-sockets配置项。利用这部分函数可以很方便地搭建起socket服务器和客户端,示例见官网

  • socket_create(int $domain , int $type , int $protocol)创建并返回一个套接字(resource类型)。其中domain指定使用的网络协议族,type指定建立的套接字类型,protocol指定使用的具体协议。
  • socket_create_listen()在某端口打开socket以接收连接。
  • socket_bind()绑定网络地址到套接字的源。
  • socket_connect()使用address作为目的地址,建立套接字连接。
  • socket_listen()在创建好socket资源,并绑定了source address后,可以调用此函数监听进入的数据流。
  • socket_accept()在依次使用socket_create创建套接字,使用socket_bind绑定端口,使用socket_listen监听连接后。该函数允许到此套接字上的连接,返回一个新的socket资源用来通信。
  • socket_read()从已连接的socket中读取一段长度的数据,返回读出的数据。
  • socket_recv()功能同上,返回字节数并将数据存放在$buf中。
  • socket_recvfrom() 从已连接和还未连接的socket中读取数据。
  • socket_write()向socket中写入数据
  • socket_send()向已连接的socket中写入数据。
  • socket_sendto()向socket中发送数据而不管是否已连接
  • socket_getsockname()socket_getpeername()获取本地和远端socket信息
  • socket_set_block()socket_set_nonblock()设置socket是否阻塞
  • socket_set_option()设置套接字选项
  • socket_shutdown()停止从socket中读写数据
  • socket_close()关闭给定的socket资源

外部拓展库

这些扩展库已经绑定在PHP发行包中,但是要编译以下扩展库,需要外部的库文件。这里仅介绍常用的cURL库。Mysqli和Mongo等可能会用得到的库介绍从略。

client URL

PHP支持Daniel Stenberg创建的libcurl库,能够连接通讯各种服务器、使用各种协议。这些curl函数在PHP 4.0.2中引入。需要安装libcurl包才能使用PHP的cURL函数。安装过程从略。curl的使用流程思路和socket,mysql等十分相似,先使用curl_init()初始化会话,再使用curl_setopt()设置选项,然后通过curl_exec()执行会话,最后使用curl_close()关闭。

  • curl_setopt()设置一个传输选项,常用的设置包括CURLOPT_URL, CURLOPT_HEADER, CURLOPT_RETURNTRANSFER, CURLOPT_TIMEOUT等。类似的,还有curl_setopt_array函数。
  • curl_reset()重置一个libcurl会话句柄的所有的选项
  • curl_exec()执行一个cURL会话。返回TRUE或执行的结果,或是FALSE。
  • curl_close()关闭一个会话,释放所有相关资源
  • curl_getinfo()获取最后一次传输的相关信息。
  • curl_error()返回一条最近一次cURL操作明确的文本的错误信息
  • curl_version()获取cURL版本信息

特色

这里列举的特点更多是PHP语言的特殊使用方式与应用特性。如HTTP用户认证(介绍见官网),cookie等。

PHP透明地支持HTTP cookie,在PHP的网络函数中可以用setcookie()setrawcookie()函数来设置cookie。cookie是HTTP标头的一部分,因此setcookie()函数必须在其它信息被输出到浏览器前调用,这和对header()函数的限制类似。可以使用输出缓冲函数来延迟脚本的输出,直到按需要设置好了所有的cookie或者其它HTTP标头。

PHP允许用户使用POST方法上传文本和二进制文件。一个上传文件的HTML表单代码类似如下,其中的MAX_FILE_SIZE隐藏字段在浏览器端限制了文件大小(单位字节,不建议依赖于此):

1
2
3
4
5
6
7
8
<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
<!-- MAX_FILE_SIZE must precede the file input field -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Name of input element determines name in $_FILES array -->
Send this file: <input name="userfile" type="file" />
<input type="submit" value="Send File" />
</form>

全局变量$_FILES自PHP4.1.0起存在,包含了所有上传的文件信息。$_FILES['userfile']数组有name, type, size, tmp_name, error等字段。error字段状态码在0-7间,分别表示上传成功/文件过大/部分上传/没有文件/找不到临时文件夹/写入失败。文件被上传后,默认地会被储存到服务端的默认临时目录中。

PHP支持同时上传多个文件并将它们的信息自动以数组的形式组织。要完成这项功能,需要在HTML表单中对文件上传域使用和多选框与复选框相同的数组式提交语法。像下面的代码那样:

1
2
3
4
5
6
<form action="file-upload.php" method="post" enctype="multipart/form-data">
Send these files:<br />
<input name="userfile[]" type="file" /><br />
<input name="userfile[]" type="file" /><br />
<input type="submit" value="Send files" />
</form>

同时,PHP还支持PUT方法上传文件,内容见官方文档。下面是一个允许用户上传图片的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
header('Content-Type: text/plain; charset=utf-8');
try {
// Undefined | Multiple Files | $_FILES Corruption Attack
// If this request falls under any of them, treat it invalid.
if (
!isset($_FILES['upfile']['error']) ||
is_array($_FILES['upfile']['error'])
) {
throw new RuntimeException('Invalid parameters.');
}

// Check $_FILES['upfile']['error'] value.
switch ($_FILES['upfile']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new RuntimeException('No file sent.');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new RuntimeException('Exceeded filesize limit.');
default:
throw new RuntimeException('Unknown errors.');
}

// You should also check filesize here.
if ($_FILES['upfile']['size'] > 1000000) {
throw new RuntimeException('Exceeded filesize limit.');
}

// DO NOT TRUST $_FILES['upfile']['mime'] VALUE !!
// Check MIME Type by yourself.
$finfo = new finfo(FILEINFO_MIME_TYPE);
if (false === $ext = array_search(
$finfo->file($_FILES['upfile']['tmp_name']),
array(
'jpg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
),
true
)) {
throw new RuntimeException('Invalid file format.');
}

// You should name it uniquely.
// DO NOT USE $_FILES['upfile']['name'] WITHOUT ANY VALIDATION !!
// On this example, obtain safe unique name from its binary data.
if (!move_uploaded_file(
$_FILES['upfile']['tmp_name'],
sprintf('./uploads/%s.%s',
sha1_file($_FILES['upfile']['tmp_name']),
$ext
)
)) {
throw new RuntimeException('Failed to move uploaded file.');
}
echo 'File is uploaded successfully.';
} catch (RuntimeException $e) {
echo $e->getMessage();
}

在php.ini文件中激活了allow_url_fopen选项后,可以在大多数需要用文件名作为参数的函数中使用HTTP和FTP的URL来代替文件名。同时,也可以在includeinclude_oncerequirerequire_once语句中使用URL。如果有合法的访问权限,以一个用户的身份和某FTP服务器建立了链接,还可以向该FTP服务器端的文件进行写操作。

持久的数据库连接是指在脚本结束运行时不关闭的连接。当收到一个持久连接的请求时。PHP将检查是否已经存在一个(前面已经开启的)相同的持久连接。如果存在,将直接使用这个连接;如果不存在,则建立一个新的连接。所谓“相同”的连接是指用相同的用户名和密码到相同主机的连接。

PHP 5.3后使用GC作为新的垃圾回收机制。每个php变量存在一个叫”zval”的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是”is_ref”,是个bool值,用来标识这个变量是否是属于引用集合(reference set)。第二个额外字节是”refcount”,用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。

安全

PHP作为一种强大的语言,无论是以模块还是CGI的方式安装,它的解释器都可以在服务器上访问文件、运行命令以及创建网络连接等。这些功能也许会给服务器添加很多不安全因素,但是只要正确地安装和配置PHP,以及编写安全的代码,那么PHP相对于Perl和C来说,是能创建出更安全的CGI程序的。这部分提出一些原则,在不同环境下尽可能提高安全性。

安装

以CGI模式安装PHP时,它的设计可以用以避免访问系统文件和服务器的任意目录。在安装时配置一些选项可以有助避免这类攻击。具体见文档介绍。同理,以Apache模块安装时,权限的注意也请见官网介绍

Session安全

这部分见Session部分的安全介绍。

文件系统

PHP被设计为以用户级别来访问文件系统,所以完全有可能通过编写一段PHP代码来读取系统文件如/etc/passwd,更改网络连接以及发送大量打印任务等等。因此必须确保PHP代码读取和写入的是合适的文件。

由于PHP的文件系统操作是基于C语言的函数的,Null字符在C语言中用于标识字符串结束,一个完整的字符串是从其开头到遇见Null字符为止。因此,任何用于操作文件系统的字符串(特别是程序外部输入的字符串)都必须经过适当的检查。

这种安全问题也会出现在执行来自用户输入的命令。通常有两条路可以选择:1)检查所有来自外部的变量(黑名单);2)后台写死可以执行的文件名或命令有限集(白名单

数据库

由于敏感数据和机密数据通常存储在数据库中,数据库安全和保护显得尤为重要。PHP本身并不能保护数据库的安全。这里只是讲述怎样用PHP脚本对数据库进行基本的访问和操作。

设计数据库时,永远不要使用数据库所有者或超级用户帐号来连接数据库,因为这些帐号可以执行任意的操作。应该为程序的每个方面创建不同的数据库帐号,并赋予对数据库对象的极有限的权限。同时,一些功能可以用视图(view)、触发器(trigger)或者规则(rule)在数据库层面完成。

连接数据库时,把连接建立在 SSL 加密技术上可以增加客户端和服务器端通信的安全性,或者SSH也可以用于加密客户端和数据库之间的连接。

存储模型中,可以散列一些没必要明文显示的数据,建议加盐散列,同时采用新的SHA散列算法(如SHA-2或SHA-3)以增加安全程度。

SQL注入

这部分内容是极为常见的网络安全问题,通过构造特殊的SQL语句,获取数据库信息甚至主机权限,介绍从略。

在预防措施上,永远不要信任外部输入的任何数据,包括表单里和cookie的信息

  • 使用权限被严格限制的帐号访问数据库
  • 检查输入的数据是否具有所期望的数据格式
  • 减少SQL语句的拼接使用
  • 使用数据库特定的敏感字符转义函数
  • 还可以选择使用数据库的存储过程和预定义指针等特性来抽象数库访问,使用户不能直接访问数据表和视图

错误报告

错误报告是一把双刃剑。一方面可以提高安全性,另一方面又有利于攻击者收集服务器的信息以便寻找弱点。PHP的独有的错误提示风格可以说明系统在运行 PHP,一个函数错误可能暴露系统正在使用的数据库,一个文件系统或者PHP的错误就会暴露web服务器具有什么权限,以及文件在服务器上的组织结构等。

有三个常用的办法处理这些问题。第一个是彻底地检查所有函数,并尝试弥补大多数错误。第二个是对在线系统彻底关闭错误报告。第三个是使用 PHP 自定义的错误处理函数创建自己的错误处理机制

可以通过error_reporting()帮助找到错误所在并使代码更安全。发布程序前,设置为E_ALL找到所有使用不当的地方;正式发布后,设为0彻底关闭错误报告或设置php.ini中的display_errorsoff

隐藏PHP

一些简单的方法可以帮助隐藏 PHP,这样做可以提高攻击者发现系统弱点的难度。在php.ini文件里设置expose_php = off,可以减少他们能获得的有用信息。

另一个策略就是让web服务器用PHP解析不同扩展名。无论是通过.htaccess文件还是Apache的配置文件,都可以设置能误导攻击者的文件扩展名。

更多机智的隐藏方法见官网隐藏PHP一节。

内核

考虑到重点所在,这部分内容仅简单地介绍一些涉及到PHP内部原理的东西。由于PHP运行在C语言的基础上,以下的内容和C语言编程靠近。

内存管理

用C语言编程时,开发者要手工地进行内存管理。因为PHP经常用作Web服务器的模块,内存管理与预防内存泄漏紧密关联。此外,Zend引擎要面对一个十分特殊的使用模式:在一段比较短的时间内,许多zval结构大小的内存块和其他的小内存块被请求又再被释放。为了满足以上的需求,Zend引擎提供为了处理请求相关数据提供了一种特殊的内存管理器。请求相关数据是指只需要服务于单个请求,最迟会在请求结束时释放的数据。API介绍从略

因为安全原因,在请求结束时,Zend引擎会释放所有由上面提到的API所分配的内存。

变量使用

PHP变量,通常来说,由两部分组成:标签(例如,可能是符号表中的一个条目)和实际变量容器。变量容器,在代码中称为zval,掌握了所需处理变量的所有数据。 包括实际值、当前类型、统计指向此容器的标签的数量,和指示这些标签是引用还是副本的标志。在PHP 5.3中,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _zval_struct zval;

typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct { /* string type */
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;

struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};

有关函数,类和对象,流等的介绍从略。

FAQ

Q: PHP 版本之间有什么联系?
A: PHP/FI 2.0是最早的PHP版本,已经不再支持。PHP 3是PHP/FI 2.0的后继者,要好很多。PHP 5是目前一代的PHP,内部使用了Zend 2引擎,除了很多新功能之外还提供了许多附加的面向对象编程(OOP)特性。


Q: 可以同时运行几个不同版本的PHP吗?
A: 可以,请参阅见PHP源程序发行包中的 INSTALL文件。


Q: 应该上哪儿去找我的php.ini文件
A: UNIX中默认在/usr/local/lib目录中,也就是<install-path>/lib。可以在编译时通过 --with-config-file-path标记来改变路径。Windows中php.ini文件的默认路径在Windows目录下。如果使用的是Apache服务器,则会首先在Apache的安装目录中寻找php.ini


Q: PHP是否仅限于处理GET和POST请求方法?
A: 不是,PHP有可能处理任何请求方法,例如CONNECT。适当的回应状态可以用header()发送。


Q: 我忘了PHP函数的参数顺序,它们是随机的吗?
A: 通常情况下,数组函数的参数里,needle在前,haystack在后;字符串函数中,haystack在前,needle在后。


Q: PHP选项register_globals对我有什么影响?
A: 强烈不建议开启此选项,register_globals会自动生成变量。


Q: 我需要直接访问请求报头中的信息,怎么能办到?
A: 如果以Apache的模块方式运行PHP,那么函数getallheaders()可以做这件事。


Q: 如果不建议使用常用散列函数保护密码, 那么我应该如何对密码进行散列处理?
A: 当进行密码散列处理的时候,有两个必须考虑的因素: 计算量以及“盐”。 散列算法的计算量越大,暴力破解所需的时间就越长。PHP 5.5提供了一个原生密码散列API, 它提供一种安全的方式来完成密码散列和验证。 PHP 5.3.7及后续版本中都提供了一个纯PHP的兼容库。PHP 5.3及后续版本中,还可以使用crypt()函数,它支持多种散列算法。针对每种受支持的散列算法,PHP都提供了对应的原生实现。


Q: “盐”是什么?
A: 加解密领域中的“盐”是指在进行散列处理的过程中 加入的一些数据,用来避免从已计算的散列值表(被称作“彩虹表”中对比输出数据从而获取明文密码的风险。


Q: 我在使用<input type="image">标记,但是没有$foo.x$foo.y变量,它们哪去了?
A: 当提交表单时,可以用图片代替标准的提交按钮,用类似这样的标记
<input type="image" src="image.gif" name="foo" />
当用户点击了图片的任何部分,该表单会被发送到服务器并加上两个额外的变量:foo.xfoo.y。因为foo.xfoo.y在PHP中会成为非法的变量名,它们被自动转换成了foo_xfoo_y。也就是用下划线代替了点。


Q: PHP 5中还能用MySQL吗?好像找不到了。
A: MySQL依然被支持,唯一区别是PHP 5中默认为不激活。这意味着在PHP的configure一行中不包含有--with-mysql选项,因此必须在编译时手工加入。Windows用户可以编辑php.ini并激活php_mysql.dll


Q: 在函数定义中,参数旁边的&是什么意思?
A: 这表示该参数是引用传递,该函数会修改其值。鼓励使用的方法是在函数定义中指定哪些参数应该用引用传递。在函数调用时通过引用传递参数是不推荐的,因为它影响到了代码的整洁。

参考