二、PHP7,以便更好地编写代码

PHP7 带来了许多新特性和变化。但是,它们都不是专门针对 REST 或 web 服务的。事实上,REST 与语言结构没有任何直接关系。这是因为 REST 是一种体系结构,而语言是为实现提供结构的。那么,这是否意味着 PHP7 有一些结构或特性可以使这个实现更好呢?是和否。这取决于我们所说的实施是什么意思。

如果我们的意思是只收到一个请求并返回一个响应,那么不,没有这样的特定功能。但是,任何 RESTful Web 服务都与实体相关,实体可以有自己的逻辑。因此,为了为该实体提供 RESTful Web 服务,我们还需要编写该逻辑。为此,我们需要编写更多的 PHP 代码,而不仅仅是获取请求并返回响应。因此,为了保持代码的简单和干净,是的,PHP7 为我们提供了很多东西。

我假设您具备 PHP 的基本知识,因为了解 PHP 基础知识是本书的先决条件。因此,我们将不考虑 PHP5。在本章中,我们将介绍 PHP7 的许多特性和更改,这些特性和更改要么非常重要,要么我们将在代码中使用。我们将直接讨论这些特性。我们不打算详细介绍安装或升级到 PHP7 的细节,因为在互联网上有几十个相关的教程。下面是我们将要讨论的功能和更改列表:

  • 标量类型声明
  • 返回类型声明
  • 零合并算子
  • 宇宙飞船操作员
  • 组使用声明
  • 与发电机相关的新功能
    • 生成器返回表达式
    • 发电机代表团
  • 匿名类
  • Closure::call()功能
  • 错误和例外
  • PHP7.1 特性
    • 可空类型
    • 对称阵列分解
    • 支持list()中的键
    • 多捕获异常处理

标量类型声明

在 PHP7 中,我们现在可以声明传递给函数的参数类型。在以前的版本中,它们只能是用户定义的类,但现在它们也可以是标量类型。标量类型是指基本的基元类型,如intstringfloat

以前,为了验证传递给函数的参数,我们需要使用某种类型的if-else。所以,我们过去常常这样做:

<?php
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 中验证参数类型的方式:

<?php
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函数。

我们可以这样做:

<?php
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

返回类型声明

与参数类型一样,还有一个返回类型;它也是可选的,但指定返回类型是安全的做法。

这就是我们如何声明返回类型的方法:

<?php
function add($num1, $num2):int{
    return ($num1+$num2);
}

echo add(2,4); //6
echo add(2.5,4); //6

正如您在2.54的情况下所看到的,它应该是6.5,但由于我们已经将int指定为返回类型,它正在执行隐式类型转换。为了避免这种情况并获得错误而不是隐式转换,我们只需启用严格类型,如下所示:

<?php
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语句。下面是一个更好地理解它的示例:

<?php
// 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 <br>";
}

如您所见,代码中有一个yield语句。与 return 语句类似,但在生成器中,yield不会一次返回所有值。它仅在每次执行 yield 时返回一个值,并且仅在调用generator函数时才调用 yield。此外,每次执行 yield 时,它都会从上次停止的位置恢复代码执行。

现在我们已经了解了生成器,让我们看看与生成器相关的 PHP7 特性。

生成器返回表达式

如前所述,在调用生成器函数时,它返回一个由 yield 表达式返回的值。在 PHP7 之前,它没有返回值的return关键字。但是由于 PHP7.0,也可以使用返回表达式。在这里,我使用了 PHP 文档中的一个示例,因为它很好地解释了这一点:

<?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()

发电机代表团

由于函数可以相互调用,同样,一个生成器也可以委托给另一个生成器。以下是生成器如何委派:

<?php
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 中也有匿名类。请注意,如果需要一个对象,那么我们很可能需要某种特定类型的对象,而不仅仅是一个随机对象,例如:

<?php
class App
{
    public function __construct()
    {
        //some code here
    }
}

function useApp(App $app)
{
    //use app somewhere
}

$app = new App();
useApp($app);

请注意,useApp()函数中需要特定类型的对象,如果该类型App不是类,则无法定义该类型。那么,我们在哪里以及为什么要使用一个具有特定功能的匿名类呢?如果我们需要传递一个实现某个特定接口或扩展某个父类的类,但只想在一个地方使用这个类,那么我们可能需要它。在这种情况下,我们可以使用匿名类。 以下是 PHP7 文档中给出的相同示例,以便您能够轻松跟进:

<?php
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()

将对象范围与闭包绑定是对不同对象使用闭包的有效方法。同时,对于不同位置的对象,使用具有不同行为的不同闭包也是一种简单的方法。这是因为它在运行时使用闭包绑定对象范围,而不需要继承、组合等。 但是之前我们没有Closure::call()方法;我们有这样的事情:

<?php
// 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
//  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
// 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接口中定义的方法列表;您可以在错误/异常处理期间相应地使用它们。毕竟,ExceptionError类都实现了相同的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 即可。

例如:

<?php

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()的简写。因此,可以在以下示例中快速看到:

<?php
$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()的缩写,[]也支持按键。

以下是前面描述的示例:

<?php
$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 中一个有趣的特性。这以前是可能的,但分多个步骤执行。现在,不再只是一次捕获一个异常并进行处理,而是提供了一个多捕获异常处理功能。这里可以看到语法:

<?php
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 的一些特性。