PHP7 带来了许多新特性和变化。但是,它们都不是专门针对 REST 或 web 服务的。事实上,REST 与语言结构没有任何直接关系。这是因为 REST 是一种体系结构,而语言是为实现提供结构的。那么,这是否意味着 PHP7 有一些结构或特性可以使这个实现更好呢?是和否。这取决于我们所说的实施是什么意思。
如果我们的意思是只收到一个请求并返回一个响应,那么不,没有这样的特定功能。但是,任何 RESTful Web 服务都与实体相关,实体可以有自己的逻辑。因此,为了为该实体提供 RESTful Web 服务,我们还需要编写该逻辑。为此,我们需要编写更多的 PHP 代码,而不仅仅是获取请求并返回响应。因此,为了保持代码的简单和干净,是的,PHP7 为我们提供了很多东西。
我假设您具备 PHP 的基本知识,因为了解 PHP 基础知识是本书的先决条件。因此,我们将不考虑 PHP5。在本章中,我们将介绍 PHP7 的许多特性和更改,这些特性和更改要么非常重要,要么我们将在代码中使用。我们将直接讨论这些特性。我们不打算详细介绍安装或升级到 PHP7 的细节,因为在互联网上有几十个相关的教程。下面是我们将要讨论的功能和更改列表:
Closure::call()
功能list()
中的键在 PHP7 中,我们现在可以声明传递给函数的参数类型。在以前的版本中,它们只能是用户定义的类,但现在它们也可以是标量类型。标量类型是指基本的基元类型,如int
、string
和float
。
以前,为了验证传递给函数的参数,我们需要使用某种类型的if-else
。所以,我们过去常常这样做:
function add($num1, $num2){
if (!is_int($num1)){
throw new Exception("$num1 is not an integer");
}
if (!is_int($num2)){
throw new Exception("$num2 is not an integer");
}
return ($num1+$num2);
}
echo add(2,4); // 6
echo add(1.5,4); //Fatal error: Uncaught Exception: 1.5 is not an integer
这里我们使用了if
来确保变量$num1
和$num2
的类型是int
,否则我们将抛出一个异常。如果您是早期的 PHP 开发人员,喜欢编写尽可能少的代码,那么很可能您甚至没有检查参数的类型。但是,如果不检查参数类型,则可能会导致运行时错误。因此,为了避免这种情况,应该检查参数类型,而这正是 PHP7 所简化的。
这就是我们现在在 PHP7 中验证参数类型的方式:
function add(int $num1,int $num2){
return ($num1+$num2);
}
echo add(2,4); //6
echo add("2",4); //6
echo add("something",4);
//Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, string given
正如您现在看到的,我们只需将 hint 输入为int
,不需要单独验证每个参数。如果参数不是整数,则应引发异常。但是,您可以看到,当2
作为字符串传递时,它没有显示TypeError
,而是进行了隐式转换,并假定它为int 2
。它这样做是因为,默认情况下,PHP 代码是在强制模式下运行的。如果启用严格模式,写入"2"
而不是 2 将导致TypeError
而不是隐式转换。要启用严格模式,我们需要在 PHP 代码的开头使用declare
函数。
我们可以这样做:
declare(strict_types=1);
function add(int $num1,int $num2){
return ($num1+$num2);
}
echo add(2,4); //6
echo add("2",4); //Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, string given,
echo add("something",4); // Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, string given
返回类型声明
与参数类型一样,还有一个返回类型;它也是可选的,但指定返回类型是安全的做法。
这就是我们如何声明返回类型的方法:
function add($num1, $num2):int{
return ($num1+$num2);
}
echo add(2,4); //6
echo add(2.5,4); //6
正如您在2.5
和4
的情况下所看到的,它应该是6.5
,但由于我们已经将int
指定为返回类型,它正在执行隐式类型转换。为了避免这种情况并获得错误而不是隐式转换,我们只需启用严格类型,如下所示:
declare(strict_types=1);
function add($num1, $num2):int{
return ($num1+$num2);
}
echo add(2,4); //6
echo add(2.5,4); //Fatal error: Uncaught TypeError: Return value of add() must be of the type integer, float returned
零合并算子
Null
凝聚算子(??
是一种句法上的糖类,但却是一种非常重要的糖类。之前在 PHP5 中,当我们有一些未定义的变量时,我们使用了ternary
运算符,如下所示:
$username = isset($_GET['username']) ? $_GET['username'] : '';
然而,现在在 PHP7 中,我们可以简单地写:
$username = $_GET['username'] ?? '';
虽然这只是一种语法上的糖分,但它可以节省时间并使代码更干净。
宇宙飞船操作员spaceship 操作符也是比较的快捷方式,在用户定义的排序函数中非常有用。我不打算详细说明这一点,因为在其文档中已经有足够的解释:http://php.net/manual/en/migration70.new-features.php#migration70.new-功能。太空船-op。
组使用声明同一名称空间中的类、函数和常量现在可以在一个use
语句中导入。在此之前,需要多个use
语句。下面是一个更好地理解它的示例:
// use statement in Pre-PHP7 code
use abc\namespace\ClassA;
use abc\namespace\ClassB;
use abc\namespace\ClassC as C;
use function abc\namespace\funcA;
use function abc\namespace\funcB;
use function abc\namespace\funcC;
use const abc\namespace\ConstA;
use const abc\namespace\ConstB;
use const abc\namespace\ConstC;
// PHP 7+ code
use abc\namespace\{ClassA, ClassB, ClassC as C};
use function abc\namespace\{funcA, funcB, funcC};
use const abc\namespace\{ConstA, ConstB, ConstC};
从本例中可以看出,group use 语句是多么方便,它是清晰可见的。带有逗号分隔值的大括号用于对值进行分组,例如{classA, classB, classC as C}
,从而生成分组的use
语句,而不是对所有这三个类分别使用use
语句三次。
尽管生成器出现在 PHP5.5 中,但大多数 PHP 开发人员都不使用它们,而且很可能不知道生成器。那么,让我们首先讨论发电机。
什么是发电机?如 PHP 手册所述:
Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the iterator interface.
好的,这里有一个更详细、更容易理解的定义,来自同一来源php.net:
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
例如,您可以简单地编写一个返回许多不同数字或值的函数。但是,问题是,如果大量不同的值意味着数百万个值,那么使用这些值生成和返回数组是没有效率的,因为它将消耗大量内存。所以在这种情况下,使用generator
更有意义。
要理解,请参见以下示例:
/* function to return generator */
function getValues($max){
for($i=0; $i<$max; $i++ ){
yield $i*2;
}
}
// Using generator
foreach(getValues(99999) as $value){
echo "Values: $value
";
}
如您所见,代码中有一个yield
语句。与 return 语句类似,但在生成器中,yield
不会一次返回所有值。它仅在每次执行 yield 时返回一个值,并且仅在调用generator
函数时才调用 yield。此外,每次执行 yield 时,它都会从上次停止的位置恢复代码执行。
现在我们已经了解了生成器,让我们看看与生成器相关的 PHP7 特性。
生成器返回表达式如前所述,在调用生成器函数时,它返回一个由 yield 表达式返回的值。在 PHP7 之前,它没有返回值的return
关键字。但是由于 PHP7.0,也可以使用返回表达式。在这里,我使用了 PHP 文档中的一个示例,因为它很好地解释了这一点:
$gen = (function() {
yield "First Yield";
yield "Second Yield";
return "return Value";
})();
foreach ($gen as $val) {
echo $val, PHP_EOL;
}
echo $gen->getReturn(), PHP_EOL;
它将给出如下输出:
First Yield
Second Yield
return Value
因此它清楚地表明在foreach
中调用生成器函数不会返回return
语句。相反,它只会以每一种收益回报。要获取return Value
,可以使用以下语法:$gen->getReturn()
。
由于函数可以相互调用,同样,一个生成器也可以委托给另一个生成器。以下是生成器如何委派:
function gen()
{
yield "yield 1 from gen1";
yield "yield 2 from gen1";
yield from gen2();
}
function gen2()
{
yield "yield 1 from gen2";
yield "yield 2 from gen2";
}
foreach (gen() as $val)
{
echo $val, PHP_EOL;
}
/* above will result in output:
yield 1 from gen1
yield 2 from gen1
yield 1 from gen2
yield 2 from gen2
*/
这里,gen2()
是gen()
中调用的另一个生成器,因此gen()
中的第三个收益率,即yield from gen2();
将控制权转移到gen2()
。因此,它将从gen2()
开始使用收益率。
Note that yield from
is only usable with arrays, traversable, or generators. Using another function (not generator) in yield from
will result in a fatal error.
You can just consider it to be similar to how we can call a function in another function.
就像匿名函数一样,现在 PHP 中也有匿名类。请注意,如果需要一个对象,那么我们很可能需要某种特定类型的对象,而不仅仅是一个随机对象,例如:
class App
{
public function __construct()
{
//some code here
}
}
function useApp(App $app)
{
//use app somewhere
}
$app = new App();
useApp($app);
请注意,useApp()
函数中需要特定类型的对象,如果该类型App
不是类,则无法定义该类型。那么,我们在哪里以及为什么要使用一个具有特定功能的匿名类呢?如果我们需要传递一个实现某个特定接口或扩展某个父类的类,但只想在一个地方使用这个类,那么我们可能需要它。在这种情况下,我们可以使用匿名类。
以下是 PHP7 文档中给出的相同示例,以便您能够轻松跟进:
interface Logger {
public function log(string $msg);
}
class Application {
private $logger;
public function getLogger(): Logger {
return $this->logger;
}
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
}
$app = new Application;
$app->setLogger(new class implements Logger {
public function log(string $msg) {
echo $msg;
}
});
var_dump($app->getLogger()); //object(class@anonymous)#2 (0) {}
正如您所看到的,尽管在$app->setLogger()
中传递了匿名类对象,但它也可以是命名类对象。因此,匿名类对象可以替换为命名类对象。但是,当我们不想再次使用同一类的对象时,最好使用匿名类对象。
将对象范围与闭包绑定是对不同对象使用闭包的有效方法。同时,对于不同位置的对象,使用具有不同行为的不同闭包也是一种简单的方法。这是因为它在运行时使用闭包绑定对象范围,而不需要继承、组合等。
但是之前我们没有Closure::call()
方法;我们有这样的事情:
// Pre PHP 7 code
class Point{
private $x = 1;
private $y = 2;
}
$getXFn = function() {return $this->x;};
$getX = $getXFn->bindTo(new Point, 'Point');//intermediate closure
echo $getX(); // will output 1
但是现在有了Closure::call()
,同样的代码可以写如下:
// PHP 7+ code
class Point{
private $x = 1;
private $y = 2;
}
// PHP 7+ code
$getX = function() {return $this->x;};
echo $getX->call(new Point); // outputs 1 as doing same thing
两个代码段执行相同的操作。然而,PHP7+代码是简写的。如果需要将某些参数传递给闭包函数,可以在对象之后传递,如下所示:
// PHP 7+ closure call with parameter and binding
class Point{
private $x = 1;
private $y = 2;
}
$getX = function($margin) {return $this->x + $margin;};
echo $getX->call(new Point, 2); //outputs 3 by ($margin + $this->x)
错误和例外
在 PHP7 中,大多数错误现在报告为错误异常。只有少数致命错误会停止脚本执行;否则,如果您正在执行错误或异常处理,它将不会停止脚本。这是因为现在Errors
类实现了一个Throwable
接口,就像Exception
类一样,它也实现了Throwable
。所以现在,在大多数情况下,通过异常处理可以避免致命错误。
以下是 error 类的一些子类:
TypeError
ParseError
ArithmeticError
DivisionByZeroError
AssertionError
这就是您捕获错误并处理它的方法:
try {
fn();
} catch(Throwable $error){
echo $error->getMessage(); //Call to undefined function fn()
}
这里,$error->getMessage()
是一个方法,它实际上以字符串形式返回此消息。在前面的示例中,消息类似于:Call to undefined function fn()
。
这不是您可以使用的唯一方法。以下是Throwable
接口中定义的方法列表;您可以在错误/异常处理期间相应地使用它们。毕竟,Exception
和Error
类都实现了相同的Throwable
接口:
interface Throwable
{
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function getPrevious(): Throwable;
public function __toString(): string;
}
PHP7.1
到目前为止,我们讨论的上述特性与 PHP7.0 相关。然而,PHP7 的最新版本是 PHP7.1,因此值得讨论 PHP7.1 的重要特性,至少是我们将要使用的特性,或者在我们的工作中值得了解和使用的特性。
为了运行以下代码,您需要安装 PHP7.1,为此,您可以使用以下命令:
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
(optional) sudo apt-get remove php7.0
sudo apt-get install php7.1 (from comments)
请记住,这不是正式的升级路径。PPA 是众所周知的,使用起来相对安全。
可空类型如果我们是类型暗示参数的数据类型或函数的返回类型,那么重要的是应该有一种方法来传递或返回NULL
数据类型,而不是将类型称为参数或返回类型。
当我们需要时,可能会有不同的场景,但我们需要做的始终是在数据类型之前放置一个?
。假设我们想要输入提示string
;如果我们想使其为空,也就是说允许NULL
,我们只需将其作为?
字符串输入 hint 即可。
例如:
function testReturn(): ?string
{
return 'testing';
}
var_dump(testReturn());
// string(10) "testing"
function testReturn2(): ?string
{
return null;
}
var_dump(testReturn2());
//NULL
function test(?string $name)
{
var_dump($name);
}
test('testing');
//string(10) "testing"
test(null);
//NULL
test();
// Fatal error: Uncaught ArgumentCountError: Too few arguments // to function test(),
对称阵列分解
这不是一个很大的功能,但它是list()
的简写。因此,可以在以下示例中快速看到:
$records = [
[7, 'Haafiz'],
[8, 'Ali'],
];
// list() style
list($firstId, $firstName) = $records[0];
// [] in PHP7.1 is having same result
[$firstId, $firstName] = $records[0];
支持列表()中的键
正如您在前面的示例中所看到的,list()
与数组一起工作,并以相同的顺序分配给变量。但是,根据 PHP7.1,list()
现在支持键。由于[]
是list()
的缩写,[]
也支持按键。
以下是前面描述的示例:
$records = [
["id" => 7, "name" => 'Haafiz'],
["id" => 8, "name" => 'Ali'],
];
// list() style
list("id" => $firstId, "name" => $firstName) = $records[0];
// [] style
["id" => $firstId, "name" => $firstName] = $records[0];
这里,在前面的代码执行之后,ID$firstId
将有7
和$firstName
将有Haafiz
,无论是使用list()
样式还是[]
样式。
这是 PHP7.1 中一个有趣的特性。这以前是可能的,但分多个步骤执行。现在,不再只是一次捕获一个异常并进行处理,而是提供了一个多捕获异常处理功能。这里可以看到语法:
try {
// some code
} catch (FirstException | SecondException $e) {
// handle first and second exceptions
}
正如您在这里看到的,有一个管道标志将这两个例外分开。所以,这个管道标志|
分隔了多个例外。本例中只有两个例外,但可能不止这些。
我们讨论了 PHP7 和 PHP7.1(PHP7 的最新版本)的新特性,这些特性要么非常重要,要么我们将在本书的其余部分中使用。然而,我们并没有完全讨论 PHP7 的特性。您可以在php.net:上找到 PHP7 功能列表 http://php.net/manual/en/migration70.new-features.php 。
在这里,您可以找到 PHP7.1 的所有新功能:http://php.net/manual/en/migration71.new-features.php 。
在本章中,我们讨论了 PHP7 的重要特性。此外,我们还介绍了新的 PHP7.1 特性。本章介绍了本书其余部分将使用的基本原理。请注意,使用 PHP7 功能不是必需的,但它有助于我们高效地编写简化的代码。
在下一章中,我们将开始在 PHP 中创建 RESTful API,正如我们在第 1 章、RESTful Web 服务、简介和动机中所讨论的那样,同时利用 PHP7 的一些特性。