现代PHP新特性

PHP4的时代为面向过程的时代,PHP5引入了类,PHP从面向过程转为了面向对象,PHP5.3引入了命令空间,此后PHP不断有新的特性使被添加,composer依赖管理工具的诞生,PHP社区制定了PSR规范等,存在诸如Symfony、Mongolog、SwiftMailer等一系列高可用组件,使得PHP可轻松开发和组织大型工程项目。这与现代PHP的新特性密不可分。以下介绍部分常用的现代特性。

命名空间

命名空间是在PHP5.3.0中引入,作用是按照一种虚拟层次结构组织代码,其层次结构类似操作系统中的文件系统的目录结构。现在的PHP组件和框架都放置在各自全局唯一的厂商命名空间中,以避免常见命名冲突。

例如: Symfony 框架中的 symfony/httpfoundation 组件

创建命名空间

1
2
3
4
5
6
7
<?php
namespace Symfony\Component\HttpFoundation;

class Response
{

}

命名空间的作用是封装和组织相关的PHP类,如同文件系统中将相关的文件放在同一个目录中管理一样。
命名空间是一个虚拟的概念,与操作系统的物理文件系统不同,没必要和文件系统中的目录结构完全对应。虽然如此,多数PHP组件为了兼容,广泛使用 PSR-4 自动加载器标准,会将子命名空间放到文件系统的子目录中。
从技术层面上看,命名空间只是PHP语言中的一种记号,PHP解释器会将其作为前缀添加到类、接口、函数、常亮名称的前面。

使用命名空间中的类、函数、常量及使用别名

1
2
3
4
5
<?php
use Symfony\Component\HttpFoundation\Response; // 使用类
use Symfony\Component\HttpFoundation\Response as Res; // 为类指定别名
use function GuzzleHttp\json_encode; // 使用函数
use constant Namespace\CONST_NAME; // 使用常量

自动加载

命名空间还为PHP-FIG制定的PSR-4自动加载标准奠定了坚实的基础,大多数现代的PHP组件都使用了这种自动加载模式,使用依赖管理器Composer可以自动加载项目的依赖。

使用接口

接口是两个PHP对象之间的契约(Contract),其目的不是让一个对象依赖另一个对象的身份,而是依赖另一个对象的能力。接口把代码和依赖解耦了,而且允许代码依赖任何实现了预期接口的第三方代码。

定义DocumentStore类

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php

// 定义DocumentStore类
class DocumentStore
{
protected $data = [];

public function addDocumet(Documentable $document)
{
$key = $document->getId();
$value = $document->getContent();
$this->data[$key] = $value;
}

public function getDocuments()
{
return $this->data;
}
}

// 定义Documentable接口
interface Documentable
{
public function getId();

public function getContent();
}

// 定义HtmlDocument类实现Documentable接口
class HtmlDocument implements Documentable
{
protected $url;

public function __construct($url)
{
$this->url = $url;
}

public function getId()
{
return $this->url;
}

public function getContent()
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
$html = curl_exec($ch);
curl_close($ch);

return $html;
}
}

// 定义StreamDocument类实现Documentable接口
class StreamDocument implements Documentable
{
protected $resource;

protected $buffer;


public function __construct($resource, $buffer = 4096)
{
$this->resource = $resource;
$this->buffer = $buffer;
}

public function getId()
{
return 'resource-' . (int)$this->resource;
}

public function getContent()
{
$streamContent = '';
rewind($this->resource);
while (feof($this->resource) === false) {
$streamContent .= fread($this->resource, $this->buffer);
}

return $streamContent;
}
}

// 定义CommandOutputDocument类
class CommandOutputDocument implements Documentable
{
protected $command;

public function __construct($command)
{
$this->command = $command;
}

public function getId()
{
return $this->command;
}

public function getContent()
{
return shell_exec($this->command);
}
}

// 使用DocumentStore类
$documentStore = new DocumentStore();

// 添加html文档
$htmlDoc = new HtmlDocument('http://php.net');
$documentStore->addDocumet($htmlDoc);

// 添加流文档
$streamDoc = new StreamDocument(fopen('stream.txt', 'rb'));
$documentStore->addDocumet($streamDoc);

// 添加终端命令文档
$cmdDoc = new CommandOutputDocument('cat /etc/hosts');
$documentStore->addDocumet($cmdDoc);

print_r($documentStore->getDocuments());

性状(Trait)

性状是类的部分实现(即常量,属性和方法),可以混入一个或多个现有的PHP类中。性状有两个作用:表明类可以做什么(像是接口);提供模块化实现(像是类)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}

class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}

生成器(Generator)

生成器会根据需求计算并产出要迭代的值。使用生成器即时算出并产出后续的值,不占用宝贵的内存资源。

1
2
3
4
5
6
7
8
9
10
11
<?php
function xrange($start, $end)
{
for ($i = $start; $i < $end; ++$i) {
yield $i;
}
}

foreach (xrange(1, 1000) as $value){
echo $value, PHP_EOL;
}

yield 每次返回一个Generator实例,foreach可遍历Iterator迭代器。

闭包(Closure)

闭包是指在创建时封装周围状态的函数。即便闭包所在的环境不在了,闭包中封装的状态依然存在。
匿名函数其实就是没有名称的函数。匿名函数可以赋值给变量,还能像其他任何PHP对象那样传递。不过匿名函数仍是函数,因此可以调用,还可以传入参数。匿名函数特别适合作为函数或方法的回调。
闭包和匿名函数实际上是Closure类的实例。

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
// 使用bindTo方法附加闭包的状态
class App
{
protected $routes = [];
protected $responseStatus = '200 OK';
protected $resposneContentType = 'text/html';
protected $responseBody = 'Hello world';

public function addRoute($routePath, Closure $routeCallback){
$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
}

public function dispatch($currentPath){
foreach ($this->routes as $routePath => $callback){
if($routePath === $currentPath){
$callback();
}
}

header('HTTP/1.1 '.$this->responseStatus);
header('Content-Type: '.$this->resposneContentType);
header('Content-Length: '.mb_strlen($this->responseBody));
echo $this->responseBody;
}
}

$app = new App();
$app->addRoute('/users/josh', function(){
$this->responseContentType = 'application/json; charset=utf-8';
$this->responseBody = '{"name": "Josh"}';
});
$app->dispatch($_SERVER['REQUEST_URI']);