PHP7新特性一览

PHP7已经出来有一段时间了,据官方测试说明,其性能与PHP5.6相比有很大的提升。PHP7相比之前的版本有了许多改变,不仅底层的引擎被改写优化,而且在语法上也带来了许多变化。可以说PHP7是PHP发展过程中的一个重要里程碑。由于现在使用PHP5版本的开发者比较多,PHP7与之前的版本存在一些差异,部分模块不能无缝过渡到新版本的环境中,所以现在使用PHP5版本的开发者还是很多的,一部分已处于观望中。本次介绍的内容是基于我在官网上了解到的一些PHP7所带来的新特性所总结的知识。

新特性

标量类型声明

我们知道,在之前写PHP的函数或者方法时,能给参数添加类型限制的只能是复杂类型(类,接口,闭包等),而arrayintstringbool等这些普通的标量类型是无法声明的,但PHP7让我们可以做到标量声明。

1
2
3
4
5
6
7
8
9
10
11
//PHP7之前
function sum($a, $b)
{
return $a + $b;
}

//PHP7
function sum(int $a, int $b)
{
return $a + $b;
}

在为参数限定了类型情况下,如果传入的参数的类型和声明的类型不一致,就会触发Fatal Error来抛出类型错误。

以下是是在PHP7中可以作为参数声明的类型

  1. class/interface 类或者接口
  2. self 自身引用
  3. array 数组
  4. callable 可调用的函数或者闭包
  5. bool 布尔
  6. float 浮点型
  7. int 整形
  8. string 字符串

返回值类型声明

跟很多脚本语言一样,PHP7之前编写的函数或者方法的返回值都是没有类型声明的,它可以任意返回各种类型而不需要声明,虽然这使得代码变得非常灵活,但在一些比较严谨的逻辑中就显得没么健壮。PHP7为函数提供了返回值声明的特性。

1
2
3
4
5
6
7
8
9
10
11
//PHP7之前
function sum($a, $b)
{
return $a + $b;
}

//PHP7
function sum(int $a, int $b): int
{
return $a + $b;
}

只需要在方法或这函数的参数括号末尾加上一个英文的冒号后接参数的类型即可。返回值可以声明的类型跟参数类型声明一样。

null合并运算符

你是否已经受够了isset($_GET['param']) && !empty($_GET['param'])这样判断,PHP7新出的null合并运算符语法糖让上面的语句变得更加精简,只需要两个?即可。

1
2
3
4
5
6
7
8
9
10
11
//PHP7之前
$param = isset($_GET['param']) && !empty($_GET['param']) ? $_GET['param'] : '';
//或者
$param = '';

if (isset($_GET['param']) && !empty($_GET['param'])) {
$param = $_GET['param'];
}

//PHP7
$param = $_GET['param'] ?? '';

相比以前的写法是不是非常的简单,??运算符会根据一个值来判断它是否是存在且不为NULL,如果是则返回第一个数,否则返回第二个数。

组合比较符

组合比较符用于比较两个量的表达式,可以比较两个量之间的大小关系。

1
2
3
4
5
6
7
8
9
10
11
12
//-1
echo 1 <=> 2;
//0
echo 2 <=> 2;
//1
echo 3.2 <=> 1.4
//-1
echo 'a' <=> 'c';
//0
echo 'b' <=> 'b';
//1
echo 'c' <=> 'b';

组合表达式返回的有三个值,大于1、等于0和小于1

define定义常量数组

之前的define只能定义int、string、float和bool等基本数据类型,现在define可以定义数组类型的常量。

1
2
3
4
define('CONST_ARRAY', [
'size' => 10,
'color' => 'red'
]);

匿名类

很巧,前段我想在PHP中使用匿名内部类,但是写了不支持,我以为既然PHP支持匿名函数应该也支持匿名类,哈哈,原来PHP7才支持,匿名类在写java程序的时候用到的非常多,有时我们不想为一个参数专门去声明一个类来适配它,而使用内部类可以直接想使用匿名函数一样方便,直接使用,如果参数类型是接口类型,我们只需要让匿名类实现指定的接口即可。

以下的java代码的内部类示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface IFoo {
void doSomething();
}

class Bar {
public void execute(IFoo executor) {
executor->soSomething();
}
}

Bar bar = new Bar();

bar.execute(new IFoo {
@Override
public doSomething() {
System.out.println('execute!');
}
});

PHP7中的内部类的形式也差不多,用class实例化一个匿名类然后根据上下文有必要地继承或者实现具体的抽象参数类型。

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
$anonymousClass = new class()
{
public function execute()
{
echo 'execute';
}
};

$anonymousClass->execute();
//输出了execute

interface IFoo
{
function doSomething();
}

class Bar
{
public function execute(IFoo $foo)
{
$foo->doSomething();
}
}

$bar = new Bar();

$bar->execute(new class () implements IFoo
{
function doSomething()
{
echo 'execute!';
}
});
//输出execute!

匿名类没有固定的名字,只在代码运行的过程中生成一个随机的类型。

闭包的绑定 Closure::call()

之前声明一个闭包之后需要为为闭包绑定执行上下文,需要复制闭包然后绑定$this,现在使用call()省去复制步骤,更加方便,性能更好。

PHP7之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class App
{

public function execute()
{
echo 'App execute';
}

}

$closure = function() {
echo $this->execute();
};

$newClosure = Closure::bind($closure, new App());
//或者$newClosure = $closure->bindTo(new App());
$newClosure(); //输出App execute

PHP7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class App
{

public function execute()
{
echo 'App execute Call';
}

}

$closure = function() {
echo $this->execute();
};

$closure->call(new App());
//输出App execute Call

unserialize 过滤

可以通过unserialize的过滤参数来设定是否过滤指定的类返回__PHP_Incomplete_Class_Name类的对象,__PHP_Incomplete_Class_Name是一个没有方法的类。具体的参数为allowed_classes,其指向需要过滤的类,默认是true,既可以对所有类都可以完全反序列化

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
62
63
64
65
66
67
68
69
70
71
class App
{
private $controller;
private $action;

function __set($name, $value)
{
if (property_exists($this, $name)) {
$this->$name = $value;
}
}

function __get($name)
{
return property_exists($this, $name)
? $this->$name
: null;
}

function __toString()
{
return "controller: {$this->controller}, action: {$this->action}";
}
}

class App2
{
private $controller;
private $action;

function __set($name, $value)
{
if (property_exists($this, $name)) {
$this->$name = $value;
}
}

function __get($name)
{
return property_exists($this, $name)
? $this->$name
: null;
}

function __toString()
{
return "CONTROLLER: {$this->controller}, ACTION: {$this->action}";
}
}

$app = new App();
$app2 = new App2();

$app->controller = 'Index';
$app->action = 'actionIndex';

$app2->controller = 'Index2';
$app2->action = 'actionIndex2';

$data = serialize($app);
$data2 = serialize($app2);

$obj = unserialize($data2, ['allowed_classes' => true]);
echo $obj, '<br>';
$obj = unserialize($data, ['allowed_classes' => 'App']);
echo $obj, '<br>';
$obj = unserialize($data2, ['allowed_classes' => 'App2']);
echo $obj, '<br>';
//报错,指定为false,任何类都被过滤,不能直接调用toString
$obj = unserialize($data2, ['allowed_classes' => false]);
echo $obj, '<br>';

命名空间批量导入

有时候需要的函数或者类在不同文件中,其是不同的命名空间下面,我们需要在要因为的php文件头部声明引入命名空间,如果引入的类和函数等非常之多,命名空间声明会很长。PHP7为引入了命名空间批量导入,可以在同一条命名空间声明语句指定具体需要的类、函数和常量等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//PHP之前
use oopsguy\framwork\core\Controller;
use oopsguy\framework\core\Model;
use oopsguy\framework\core\Config;

use function oopsguy\framework\core\function\func1;
use function oopsguy\framework\core\function\func2;

use const oopsguy\framework\core\constant\CONST_SESSION_KEY;
use const oopsguy\framework\core\constant\CONST_MANAGER_SESSION_KEY;

//PHP7
use oopsguy\framework\core\{Controller, Model, Config};
use function oopsguy\framework\core\function\{func1, func2};
use const oopsguy\framework\core\constant\{CONST_SESSION_KEY, CONST_MANAGER_SESSION_KEY};

新特性使得命名空间的声明语句变得简洁许多,精简了很多代码。

生成器返回表达式

生成器是一个偏的内容,不太被使用的特性,不过这在PHP5.5版本就已经开始有了。以下是官方对生成器的解释:

生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。
生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

其实平时我们在遍历内容比较大的数据时,很消耗内存,但是使用生成器就不一样,它只有在迭代的时候才会取值,不会把遍历的数据全部放入内存中。

生成器Generator实现了迭代器接口Iterator,可以对其进行遍历操作,在PHP7中生成器添加了getReturn接口,可以获取return返回值,而在PHP7之前,在生成器中声明带值的返回会引发Fatal error: Generators cannot return values using "return" in %s on line %d错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function generateSerialNo(int $limit)
{
$i = 0;

for (; $i < $limit; $i++) {
yield $i;
}

return $i;
}

$gen = generateSerialNo(10);

foreach ($gen as $value) {
echo $value, ' ';
}

echo $gen->getReturn();

//输出
//0 1 2 3 4 5 6 7 8 9 10

以上时生成一个0$limt-1的序列数字,可以看到最后输出10,其实这个10时返回值,当生成器遍历完之后$i的值已经时10了,跳出了循环。

生成器委托

生成器实现了Iterator接口,说明其可以进行遍历操作,在PHP7中,可以用yield from来把生成器委托到其它生成器。

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
//生成1到5的数字
function generateOneToFive()
{
yield from generateOneToThree();
yield from generateSixFourToFive();
}

//生成1到3的数字
function generateOneToThree()
{
yield 1;
yield 2;
yield 3;
}

//生成4到5的数字
function generateSixFourToFive()
{
yield 4;
yield 5;
}

foreach (generateOneToFive() as $num) {
echo $num . ' ';
}

//输出
//1 2 3 4 5

上边的代码需要生成器生成1-5的数字,主生成器中并没有生成数字的逻辑,而是委托其它生成器完成。

整除函数intdiv

这应该很好了解,整除,返回的是整数部分的模。

1
2
3
4
5
echo intdiv(100, 3) , '<br>';
echo intdiv(8, 7) , '<br>';
echo intdiv(10.4, 2.5) , '<br>';
//结果
//33 1 5

Session配置

现在我们可以在session_start时候传入一个数组配置参数来覆盖运行时的php.ini文件的配置参数。

1
2
3
4
session_start([
'cache_limiter' => 'private',
'read_and_close' => true,
]);

list解构

在PHP7中,使用list不仅解构数组,还可以解构实现了ArrayAccess接口的对象。

list只能用于数字索引的数组

以下是一个配置类的示例

配置文件config.php内容

1
2
3
4
<?php
return [
'value1', 'value2'
];

配置类

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
class Config implements ArrayAccess
{
private $data = [];

function __construct($file)
{
/** @noinspection PhpIncludeInspection */
$this->data = include $file;
}

public function offsetExists($offset)
{
return isset($this->data[$offset]);
}

public function offsetGet($offset)
{
return ($this->offsetExists($offset))
? $this->data[$offset]
: null;
}

public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}

public function offsetUnset($offset)
{
if ($this->offsetExists($offset))
unset($this->data[$offset]);
}
}

$config = new Config('config.php');
echo $config[1] , '<br>';
list($key, $key1) = $config;
echo $key , '<br>';
echo $key1 , '<br>';

Unicode codepoint 转译语法

echo可以接受一个已16进制形式的Unicode codepoint,CodePoint即完整的Unicode的代码,PHP经过解析打印出一个双引号或heredoc包围的 UTF-8 编码格式的字符串。

1
2
3
4
5
6
7
echo "\u{222}", '<br>';
echo "\u{444}", '<br>';
echo "\u{666}", '<br>';
//输出
//Ȣ
//ф
//٦

IntlChar

这事PHP7新增的一个类,IntlCHar类提供了一些列的工具方法来访问和操作Unicode字符相关的信息,类中定义来大量的常量。这个类依赖ICU库。

1
2
3
4
5
echo IntlChar::charName('@') . '<br>';
echo IntlChar::charName('&') . '<br>';
//输出
//COMMERCIAL AT
//AMPERSAND

以上只是讲了PHP7特性的大部分,还有其它特性

Expectations 预期

在PHP7中assert不再是一个函数,而是一个语言结构就像echo一样。

预期增强了之前的assert方法,我们可以在开发或者生产环境中使用断言,其提供了可配置选项,我们可以针对不同的环境来使用不同的策略。

PHP7的断言配置:

  1. zend.assertions
    • 1-生成和执行代码(开发坏境)
    • 0-生成代码但跳过运行时环境
    • -1-不生成代码(生产环境)
  2. assert.exception
    • 1-断言失败时,抛出异常,没有指定异常则默认抛出AssertException
    • 0-生成一个Throwable抛出警告,而不是抛出错误(兼容PHP5的行为)。
1
2
3
4
5
6
7
8
9
10
11
ini_set('zend.assertions', 1);

$dest = true;
assert(false, $dest); //抛出AssertException

//----------------------------
ini_set('assert.exception', 0);
ini_set('zend.assertions', 0);

$dest = true;
assert(false, $dest); //无错误和警告

函数preg_replace_callback_array()

这个新函数能够以数组的形式来传递正则和回掉函数处理匹配工作,比使用preg_replace_callback()精简了不少,特别适合批量匹配正则。

1
2
3
4
5
6
7
8
preg_replace_callback_array([
'#\d#i' => function($match) {
echo 'd: ' . $match[0], '<br>';
},
'#\w#i' => function($match) {
echo 'w: ' . $match[0], '<br>';
}
], '2d3w4g5t');

输出:

1
2
3
4
5
6
7
8
d: 2
d: 3
d: 4
d: 5
w: d
w: w
w: g
w: t

总结

以上是PHP的几乎全部特性,当然还有一些不是很常见的,如CSPRNG,这也是新特性,不过由于本人也没有太了解,所以就不介绍了,有兴趣的朋友可以在自己的PHP7项目中使用。