PHP 被开发为一个开源项目,并作为一个开源项目进行维护,数百(也可能是数千)名贡献者积极致力于该语言的改进,使之能够满足现代 Web 开发的需要。PHP 不断吸纳新的编程思想,借用其他编程语言的想法,同时还维持了高水平的向后兼容性。这些品质使得 PHP 目前处于突出的地位:该语言支持着大约 82% 的 Web应用,并支持最大的一些网站(比如 Facebook)。PHP 是内容管理系统 (CMS) 框架背后的核心技术,这些框架包括 WordPress、Drupal、Magento 和 Joomla!(它们的总和占所有 Web 应用的 30%)。
如果您很长一段时间(甚至是过去几年)没有看过 PHP,那么您可能已经不认识它现在的样子。本文是由四个部分组成的系列文章的第一个部分,在本文中,将向您介绍最新版本中添加的最新特性,这些版本包括 PHP 5.3、5.4 和 5.5。
当然,PHP 的改变不是凭空发生的,新的语言特性只是 PHP 整体改进的一部分。PHP 程序员组装其开发服务器、管理其第三方库和解决 Web 安全问题的方式也在发生改变。在本系列的后续文章中,将介绍改进 PHP 生态系统的一些方面。
“我不认为有人偷了任何东西;我们只是借用而已。”
B.B. King
命名空间
关于本系列
PHP 在活动开源项目的庇护下不断改进,目前为许多 Web 应用提供支持。PHP 早期是一种模块语言,从那时到现在,PHP 已经历了显著的变化。如果您多年没有使用过或评估过 PHP 技术,那么您很可能几乎认不出当前的一些 PHP 项目。本系列文章将向您展示最新的 PHP 功能,以及如何使用当今的 PHP 来构建现代化的、安全的网站。
命名空间是一项编程功能,旨在允许不同库中的类(和函数)具有相同的名称。随着 PHP 成长成为一种语言,以及代码库的重用变得更加普遍,名称冲突开始成为一个主要问题。通过将每个库细分到它们自己的命名空间,您可以安装和使用第三方库(没有不良后果),该库包含名称与您的名称相匹配的所有类。
在支持建立命名空间之前,库解决了这个问题:通过在所有类前面加上一致的短语,比如 Zend 框架前面附加的 Zend_。您可以用深奥的类名称来结束命名,比如Zend_Db_Table,而且您还需要在编码时重复键入这样的前缀。在(急需)将 DateTime类添加到 PHP 5.2 版的核心中时,问题出现了。突然之间,许多开源库开始崩溃,因为它们创建了名为 DateTime 的自己的类来填补这个缺口。
命名空间是通过 namespace 关键字创建的,并用反斜杠 (\) 进行分隔。清单 1 显示了一个简单的示例。
清单 1. 简单的命名空间使用
<?php
namespace zebra;
class DateTime
{
public function __construct() {
echo "Today!";
}
}
function stripes() {
echo "-=-=-=-=-=-=-=-";
}
在 清单 1 中,我定义了自己的命名空间,称之为 zebra,然后我还在该空间中定义了一个类和一个函数。在这种情况下重新定义 DateTime 类不会带来任何问题或错误,因为我在命名空间中创建了自己的 DateTime 版本。现在,我可以通过引用全名(使用 \ 作为分隔符)来使用命名空间,如清单 2 所示。
清单 2. 使用一个自定义命名空间
<?php
include 'listing1.php';
// Use the stripes function I declared in my namespace:
zebra\stripes();
// Use my own DateTime class:
$dt = new zebra\DateTime();
// Now use the 'root' level Datetime class:
$real = new \DateTime('tomorrow noon');
echo $real->format(\DateTime::ATOM);
在清单 2 的第 2 行上,include 以 namespace 指令开头的文件 (listing1.php)。然后,我可以通过使用 zebra\ 作为前缀来引用我的类和函数。我还可以使用全局类,比如原始 DateTime,通过附加反斜杠来指示全局命名空间。
清单 2 中的技术非常方便,但还有一个方法可以让代码看起来更简洁:新的 use 关键字。这个关键字指出,您想要从该命名空间中直接访问某个特定的类,如清单 3 所示。
清单 3. 使用 use 包含命名空间
<?php include 'listing1.php'; use zebra\DateTime; // Use our own DateTime class: $dt = new DateTime();
您还可以使用 use 关键字创建别名,这样您就可以将任何类重命名为您的范围内的其他名称。清单 4 显示了如何创建一个别名。
清单 4. 创建一个别名
<?php include 'listing1.php'; use zebra\DateTime as ZDT; // Use our own DateTime class: $dt = new ZDT();
您可以使用命名空间做比我这里介绍的更多的事情,包括创建次级命名空间。您可以深入研究有关的官方文档。
特征(Traits)
在传统上,面向对象的编程基于类和对象的概念,这些类和对象继承自其他类和对象。您可以从一个抽象概念开始了解这些,然后逐渐了解具有子类的次级类,获得更具体的细节。如果您需要在对象之间使用一个一致的 API,那么您还应该了解接口的概念;在那里,您可以定义对象需要实现的方法。但是,如果不仅想要声明 哪些方法必须存在,还想在同一时间提供自己的实现,该怎么做?让我们来了解一下特征(traits)。
特征(PHP 5.4 中添加的特性)是一个适用于水平代码重用(而继承是垂直代码重用)的工具。在其他语言中,该特性有时被称为混合(mixin)。这个概念在两种情况下都很简单。特征或混合是开发任意数量的方法的一种方式。也许您有一些常用方法来过滤和操作一些对象将会分享的数据或业务逻辑。您可以将它们保存在一个特征中,然后在您可能希望使用的任何类中重用它们。
清单 5 显示了一个简化的示例,该示例提供了一个日志记录方法,任何类都可以使用该方法作为记录事件的统一方法。
清单 5. 声明并使用一个特征
<?php
trait logging {
private static $LOG_ERROR = 100;
private static $LOG_WARNING = 50;
private static $LOG_NOTICE = 10;
protected $log_location = 'output.log';
protected function log($level, $msg) {
$output = [];
$output[] = "Class: ".__CLASS__.' | ';
$output[] = "Level: {$level} | ";
$output = array_merge($output, (array)$msg, ["\n"]);
file_put_contents($this->log_location, $output, FILE_APPEND);
}
}
class User {
use logging;
public function __construct() {
$this->log(self::$LOG_NOTICE, 'New User Created');
}
}
class DB {
use logging;
private $db = 'localhost:8080';
public function connect() {
// ... attempt to connect and fail:
$this->log(self::$LOG_ERROR, ['Connection Failed-', $this->db]);
}
}
在 清单 5 中,第 2 行的开头处声明了 trait logging。请注意,这个特征包含一个方法,以及大量的属性(包括静态属性)。从表面看,该声明看起来类似于类的声明,但它使用了 trait 关键字。
进一步深入研究清单 5,将特征带到 User 和 DB 类中,我再次使用了 use 关键字。类定义的顶部的 use logging; 指令实质上将所有属性和方法从 logging 特征拉入了本地类中。现在,每个类都能直接访问所有日志工具,无需单独实现它们。特征内部使用的 __CLASS__ 变量成为了在此时使用该特征的类的名称,因而支持立即为类定制日志消息。
闭包(即匿名函数)
在旧版的 PHP 中,您可以通过 create_function,以编程方式创建函数,它们允许为传递功能提供一个解决方法:以字符串的形式发送函数的名称,然后通过 call_user_func 和 call_user_func_array 来调用函数。这种选择缺乏真正匿名函数的优雅,真正的匿名函数可以在方法和类之间传递函数名称,或者在适当的范围内将它们保存在变量中。
匿名函数在 JavaScript 中无处不在,不知道 JavaScript 的 PHP 程序员很少见。所以扩展 PHP 来使之包含匿名函数是自然而然的事情。自 PHP 5.3 起,您可以在能够使用变量(用于存储或传递)的任何地方使用普通的函数声明语法。
作为一个示例,清单 6 显示了以前使用内置排序函数来指定您自己的自定义排序功能的方式。
清单 6. 以前传递函数的方式
<?php
$insurees = [
'u4937' => ['name' => 'Thomas Smythe', 'age' => 33],
'u1282' => ['name' => 'Gayle Runecor', 'age' => 25],
'u9275' => ['name' => 'Sara Pinnicle', 'age' => 57],
'u2078' => ['name' => 'Delilah Shock', 'age' => 41],
];
function insuree_age_sort($a, $b) {
if ($a['age'] == $b['age']) { return 0; }
return ($a['age'] > $b['age']) ? -1 : 1;
}
uasort($insurees, 'insuree_age_sort');
清单 6 有些繁琐,因为您需要在相同的范围内定义一个函数,然后使用它(即使您永远不会再使用它)。通过使用闭包,您现在就可以只使用一个步骤来直接创建和使用该函数。清单 7 显示了一个这样的示例,这是一个更优雅的解决方案。
清单 7. 使用一个匿名函数来实现排序
<?php
uasort($insurees, function ($a, $b) {
if ($a['age'] == $b['age']) { return 0; }
return ($a['age'] > $b['age']) ? -1 : 1;
});
不过,有人宣称这小的用例是提供此特性的惟一理由。但意识到这里发生的一切之后,我发现,我可以动态创建函数,并将它传入uasort(),后者是一个一级的变量。您可以在变量中存储函数,并将它们传递给不同的函数和类。当您看到被添加到 PHP 和闭包中的作用域特性时,闭包的真正力量就会变得显而易见。
借助被广使用的 use 关键字,您可以在函数有权访问的某个作用域内指定某些变量。通过这种方式,您可以处理相当复杂的一些细节,而不必在每次以变量形式访问函数时不断地将这些细节传递到函数中。清单 8 和清单 9 中(人为设计)的示例展示了这种力量。
清单 8 在一个回调中使用了继承的变量作用域。
清单 8. 在回调中使用继承的变量作用域
<?php
// Find only people over a certain age
$minage = 40;
$over = array_filter($insurees, function($a) use ($minage) {
return ($a['age'] >= $minage);
});
清单 9 使用了包含多个变量和直接调用的闭包。
清单 9. 包含多个变量和直接调用的闭包
<?php
$urls = [
'training' => '/training',
'magazine' => '/magazine',
't-shirt' => '/swag/tshirts',
];
$current = $_SERVER['REQUEST_URI']; // May come from somewhere else
// Helper for links, ignoring links if we are on that page:
$link = function($name) use ($urls, $current) {
if ($current == $urls[$name]) {
return $name;
} else {
return "<a href=\"{$urls[$name]}\">{$name}</a>";
}
};
?>
<p>Welcome to our website! Make sure to check out
our <?= $link('training') ?> offerings, see the
latest issue of our <?= $link('magazine'); ?>,
and don't forget to check out our latest
<?= $link('t-shirt') ?> designs as well.</p>
如果您习惯在 JavaScript 中使用闭包,那么您应该已经熟悉它们的能力、灵活性和有时有点危险的特性。
生成器
在发布 PHP 5.0 的之后,人们开始使用标准 PHP 库 (SPL)。SPL 是解决某些计算机科学问题的一组标准方法,比如创建队列和链表(并提供可扩展的特性,比如类文件自动加载机)。
SPL 中包含一个叫做 iterator 的特性。Iterator 是一个接口(包含由预先构建的类组成的一个集合),您可以使用该接口让任何类都能够通过foreach 关键字进行循环,就像它是一个数组那样。这个神奇的发明使人们能够采用统一的方式来利用所有 “列表”。但 Iterator 是一个相当复杂的系统,需要您创建一个类,并定义了 4 种方法。有时您希望能够使用一个标准的 foreach 循环,但不希望因为使用一个类结构来存储它而产生开销。
利用较新的生成器 特性,您可以(通过 yield—关键字)让某个函数生成一个值列表,并一次返回一个值。实际上,您可以生成 您想要生成的尽可能多的值,而不必返回任何值。然后,您可以对您的函数使用一个 foreach 循环来检索函数想要收回的所有值。
清单 10 显示了一个简单的示例,一个函数将某一范围内的值划分为相等的部分并返回它们。
清单 10. 一个用来划分各个部分的生成器
<?php
function parts($start, $end, $parts) {
// Find what our actual length is:
$length = $end - $start;
do {
$start += $length / $parts;
yield $start;
} while ($start < $end);
}
// Break 5 feet into 3 parts:
foreach (parts(0, 5, 3) as $l) {
echo $l, " ";
}
echo "\n";
// Break the range 10-90 into 12 parts:
foreach (parts(10, 90, 12) as $l) {
echo $l, " ";
}
echo "\n";
第 7 行上发生了神奇的事情,该行上使用了 yield 关键字。实质上,此时函数将停止执行,并返回生成的值。在该函数的后续的每次调用中,都会从它上次停止的地方开始执行,直到生成下一个值或函数终止。
该例子的人为痕迹非常明显,但您可以想象如何使用此技术来获得数据库查询结果,或者使用该技术来解析某个 XML 文件返回的结果。您甚至可以生成键和值,通过使用语法 yield $key => $value 直接模仿数组,如清单 11 中的基于 XML 的示例所示。
清单 11. 使用一个生成器来处理 XML
<?php
$xml = <<<EOXML
<?xml version="1.0" encoding="UTF-8" ?>
<products>
<books>
<book isbn="978-1940111001">Mastering the SPL Library</book>
<book isbn="978-1940111056">Functional Programming in PHP</book>
<book isbn="978-0981034508">Guide to Date and Time Programming</book>
<book isbn="0973589825">Guide to PHP Design Patterns</book>
</books>
</products>
EOXML;
$books = function () use ($xml) {
$products = simplexml_load_string($xml);
foreach ($products->books->book as $book) {
yield $book['isbn'] => (string)$book;
}
};
foreach ($books() as $isbn => $title) {
echo "{$isbn}: {$title}\n";
}
许多的新特性
PHP 中的新特性太多,无法在这里详细介绍。表 1 是最近几年中新添加的其他一些特性的快速列表。
表 1. 其他新的 PHP 语言特性
| 特性 | 版本 | 描述 |
|---|---|---|
| 延迟静态绑定 | 5.3 | 父类调用一个静态方法或属性的能力已通过其子类/继承类得到定义(与通常采用的方式相反)。例如,允许通用功能存在于从其扩展子类进行配置的某个父类中。 |
| Nowdoc 语法 | 5.3 | 能够指定一个字符串作为一个文本块,其中的任何变量都不会被解释。 |
| 快捷三元运算符 (?:) | 5.3 |
可以省略标准三元运算符的中间部分,这样,true 条件默认值就会回到原始比较值。示例:$result = $value ?: 0;
|
跳跃标签 (goto)
|
5.3 |
虽然有些人并不认为可以使用语言来实现 “向前移动”,但随着 goto 操作符的添加,某些编码练习(比如创建状态机)可以在 PHP 中轻松实现。
|
神奇的方法__callStatic__invoke__debugInfo
|
5.3 5.3 5.6 |
这三个神奇的新方法被添加到其他可用方法中,在设计您的类时,PHP 5.0 可以完成您可以使用的选项的强大内定功能。现在,您可以使用过载的和未定义的静态方法,像调用函数一样调用对象,并控制调试您的对象时的输出。 |
一直可用的短代码回响 (<?=)
|
5.4 |
在以前,如果您在 PHP 中禁用短代码,那么所有变体都将停止使用。自 PHP 5.4 起,<?= 短代码(模板中的通用短代码)将一直对您可用。
|
| 短数组语法 | 5.4 |
现在,您可以使用括号(比如 [1,2,3]),而不是将您的数组声明为 array(1,2,3)。
|
| 内置 Web 服务器 | 5.4 | PHP 运行时现在提供了一个内置的 Web 服务器,使其能够更轻松地实现简单的代码测试和开发,无需配置 Apache 或 IIS。 |
结束语
现代 PHP 开发看起来一点也不像过去的旧程序代码。此外,PHP 继续维持着一个较高的发展速度。PHP 7 已经提上日程,计划在 2015 年末发布。
本系列的下一个部分将查看针对密码保护的不断变化的需求领域,PHP 一直都在帮助 Web 开发人员处理这个复杂的需求。
