• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • PHP 8.0 新特性

    PHP 8.0作为 PHP 语言的一个版本更新。它包含了很多新功能与优化项,包括命名参数、联合类型、注解、构造器属性提升、match 表达式、nullsafe、运算符、JIT,并改进了类型系统、错误处理、语法一致性。

    类型系统与错误处理的改进:

    • 算术/位运算符更严格的类型检测。
    • Abstract trait方法的验证。
    • 确保魔术方法签名正确。
    • PHP 引擎 warning 警告的重新分类。
    • 不兼容的方法签名导致 Fatal 错误。
    • 操作符@不再抑制 fatal 错误。
    • 私有方法继承。
    • Mixed类型。
    • Static 返回类型。
    • 内部函数的类型 Email thread。
    • 扩展 Curl、 Gd、 Sockets、 OpenSSL、 XMLWriter、 XML 以 Opaque 对象替换 resource。

    其他语法调整和改进:

    • 允许参数列表中的末尾逗号、闭包 use 列表中的末尾逗号。
    • 无捕获的 catche。
    • 变量语法的调整。
    • Namespace 名称作为单个 token。
    • 现在 throw 是一个表达式。
    • 允许对象的::class。

    新的类、接口、函数:

    • Weak Map 类。
    • Stringable 接口。
    • str_contains()、 str_starts_with()、 str_ends_with()。
    • fdiv()。
    • get_debug_type()。
    • get_resource_id()。
    • token_get_all()对象实现。
    • New DOM Traversal and Manipulation APIs。


    字符串与数字的比较逻辑

    // PHP 8.0 之前
    0 == 'foobar' // true
    
    // PHP 8.0
    0 == 'foobar' // false
    

    PHP 8 比较数字字符串(numeric string)时,会按数字进行比较。不是数字字符串时,将数字转化为字符串,按字符串比较。


    新增字符串函数

    对于字符串的一些操作。

    • str_contains():字符串中是否包含某个字符串。
    • str_starts_with():字符串是否以某个字符串开头。
    • str_end_with():字符串是否以某个字符串结尾。
    // PHP 8.0 之前
    // 1. 字符串中是否包含某个字符串
    $str = 'Hello world!';
    if (strpos($str,"world") !== false){
        echo "Found";
    }
    // 2. 字符串是否以某个字符串开头
    $found = 'Hello';
    if (substr($str,0,count($found)) == $found){
        echo 'Yes';
    }
    // 3. 字符串是否以某个字符串结尾
    if (substr($str,-count($found),count($found)) == $found){
        echo 'Yes';
    }
    
    
    // PHP 8.0
    // 1. 字符串中是否包含某个字符串
    $str = 'Hello world!';
    if (str_contains($str,"world")){
        echo 'Found';
    }
    // 2. 字符串是否以某个字符串开头
    if (str_starts_with($str,"Hello")){
    	echo 'Yes';
    }
    // 3. 字符串是否以某个字符串结尾
    if (str_end_with($str,'Hello')){
        echo 'Yes';
    }
    


    null 空值安全检查(空安全运算符)

    PHP8 中引入了 nullsafe 运算符,允许在一个对象链式调用对象方法或属性。在链式调用过程中,某个对象方法或属性可能是空的,如果是空的,就会报null错误。nullsafe 就很好的解决了这个问题。

    ?->
    

    例如:用户登录后,在 blade 模板中显示用户名,我们会这么调用:auth()->user()->name,假设 user 中没有 name,那么这样调用就会报错。现在就用 PHP8 中的方法来解决这个问题。

    // 案例一
    Route::get('a', function () {
       return auth()?->user()?->name;
    });
    
    // 案例二
    Route::get('b', function () {
       return auth()?->user()?->name;
    });
    

    案例一:当auth()调用user()时,user 再使用?->去调用 name 属性,如果 name 为null,那么就直接返回null

    案例二:当auth()调用user()时,使用?->,就是在说,有user()这个对象吗,有的话继续使用user()调用其值,没有的话就直接返回null


    构造器属性提升

    现在我们可以直接在构造器__construct中直接声明类的属性。它简化类中属性声明并进行初始化赋值的操作。

    // PHP 8.0 之前
    class Client
    {
        private string $url; 
        public function __construct(string $url)
        {
            $this->url = $url;
        }
    }
    
    // PHP 8.0
    class Client
    {
        public function __construct(
            private string $url,
        ) {}
    }
    
    // PHP 8.0 之前
    class Point {
      public float $x;
      public float $y;
      public float $z;
      public function __construct(
        float $x = 0.0,
        float $y = 0.0,
        float $z = 0.0
      ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
      }
    }
    
    
    // PHP 8.0
    class Point {
      public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
      ) {}
    }
    
    // PHP 8.0 之前
    class User{
        public $name;
        public function __construct($name){
            $this->name = $name;
        }
    }
    $user = new User("lisa");
    $user->name; 
    
    
    // PHP 8.0
    class User{
        public function __construct(public $name){        
        }
    }
    $user = new User("lisa");
    $user->name; 
    


    联合类型

    联合类型(Union Types)特性,用在类型提示或返回值有一个或者多个类型时。

    // PHP 8.0 之前
    class PostService
    {
        public function all(): mixed
        {
            if (! Auth::check()) {
                return [];
            }
     
            return Post::query()->get();
        }
    }
    
    // PHP 8.0
    class PostService
    {
        public function all(): array|Collection
        {
            if (! Auth::check()) {
                return [];
            }
     
            return Post::query()->get();
        }
    }
    
    // PHP 8.0 之前
    class Number {
      /** @var int|float */
      private $number;
      /**
       * @param float|int $number
       */
      public function __construct($number) {
        $this->number = $number;
      }
    }
    new Number('NaN'); // Ok
    
    
    // PHP 8.0
    class Number {
      public function __construct(
        private int|float $number
      ) {}
    }
    new Number('NaN'); // TypeError
    

    相较于以前的 PHPDoc 声明类型的组合,现在可以用原生支持的联合类型声明取而代之,并在运行时得到校验。


    命名参数

    命名参数能使我们的编码更加声明式–比如,不用再去猜测所调用函数的第三个参数指的什么。举

    // PHP 8.0 之前
    class ProcessImage
    {
        public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
        {
            // logic for handling image processing
        }
    }
     
    ProcessImage::handle('/path/to/image.jpg', 500, 300, 'jpg', 100, 5);
    
    // PHP 8.0
    class ProcessImage
    {
        public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void
        {
            // logic for handling image processing
        }
    }
     
    ProcessImage::handle(
        path: '/path/to/image.jpg',
        height: 500,
        width: 300,
        type: 'jpg',
        quality: 100,
        compression: 5,
    );
    

    如上例所示– height 和 width 参数如果搞混了,可能会出现预期之外的结果。如果像上例这样,类和实例彼此紧挨着,那问题不大。但是如果这个方法来自于你从其他地方引入安装的一个包里,而这个包文档可能也没做好–这种情况下,使用命名参数将有助于包的使用者理解参数的顺序。不过使用该特性需要谨慎,因为包作者倾向于频繁地修改参数名称,并且通常不会将其当作破坏性修改。


    Match 表达式

    以前条件判断switch语句,当有多个caseMatch表达式对此作了改进。

    // PHP 8.0 之前
    switch (8.0) {
      case '8.0':
        $result = "Oh no!";
        break;
      case 8.0:
        $result = "This is what I expected";
        break;
    }
    echo $result;  //Oh no!
    
    
    // PHP 8.0
    echo match (8.0) {
      '8.0' => "Oh no!",
      8.0 => "This is what I expected",
    };
    // This is what I expected
    

    新的 match 类似于 switch,并具有以下功能:

    • Match 是一个表达式,它可以储存到变量中亦可以直接返回。
    • Match 分支仅支持单行,它不需要一个break;语句。
    • Match 使用严格比较。
    // PHP 8.0 之前
    switch (string $method) {
        case 'GET':
            $method = 'GET';
            break;
        case 'POST':
            $method = 'POST';
            break;
        default:
            throw new Exception("$method is not supported yet.");
    }
    
    // PHP 8.0
    match (string $method) {
        'GET' => $method = 'GET',
        'POST' => $method = 'POST',
        default => throw new Exception(
            message: "$method is not supported yet.",
        ),
    };
    

    在 PHP8 种,Match语句,使用了更加精简的语法,且更又可读性。


    在实例中使用::class

    过去,如果你像传入类字符串到方法中,你必须使用类似于get_class这样的东西,看起来似乎没有意义。系统在调用时已经知道这个类了,因为你已经自动加载或创建了实例。如下例:

    // PHP 8.0 之前
    $commandBus->dispatch(get_class($event), $payload);
    
    // PHP 8.0
    $commandBus->dispatch(
        event: $event::class,
        payload: $payload,
    );
    
    // PHP 8.0 之前
    class User {    
    }
    $className = User::class; 
    
    // or 
    $user = new User();
    get_class($user);
    
    
    // PHP 8.0
    
    class User {    
    }
    $user = new User();
    $className = $user::class;
    


    无捕获的 catch 块

    有时候,你并不需要获取抛出的异常,虽然并不一定常见。

    // PHP 8.0 之前
    try {
        $response = $this->sendRequest();
    } catch (RequestException $exception) {
        Log::error('API request failed to send.');
    }
    
    // PHP 8.0
    try {
        $response = $this->sendRequest();
    } catch (RequestException) {
        Log::error('API request failed to send.');
    }
    


    箭头函数支持异常捕捉

    // PHP 8.0 之前
    $fn = fn() => 'ABC';
    $fn();
    $ex = fn() => throw new Exception(); // error
    
    // PHP 8.0
    $fn = fn() => 'ABC';
    $fn();
    
    $ex = fn() => throw new Exception(); 
    
    try{
        $ex();
    }catch(Exception){
        echo 'Failed';
    }
    

    对比:修复了箭头函数对异常的处理。并且可以去点捕捉异常时的参数名$exception(如果没有用到这个变量)。


    注解功能

    注解功能提供了代码中的声明部分都可以添加结构化、机器可读的元数据的能力,注解的目标可以是类、方法、函数、参数、属性、类常量。通过反射 API可在运行时获取注解所定义的元数据。因此注解可以成为直接嵌入代码的配置式语言。

    通过注解的使用,在应用中实现功能、使用功能可以相互解耦。某种程度上讲,它可以和接口(interface)与其实现(implementation)相比较。但接口与实现是代码相关的,注解则与声明额外信息和配置相关。接口可以通过类来实现,而注解也可以声明到方法、函数、参数、属性、类常量中。因此它们比接口更灵活。

    注解使用的一个简单例子:将接口(interface)的可选方法改用注解实现。我们假设接口 ActionHandler 代表了应用的一个操作:部分 action handler 的实现需要 setup,部分不需要。我们可以使用注解,而不用要求所有类必须实现 ActionHandler 接口并实现 setUp()方法。因此带来一个好处——可以多次使用注解。

    // PHP 8.0 之前
    
    class PostsController
    {
        /**
         * @Route("/api/posts/{id}", methods={"GET"})
         */
        public function get($id) { /* ... */ }
    }
    
    // PHP 8.0
    class PostsController
    {
        #[Route("/api/posts/{id}", methods: ["GET"])]
        public function get($id) { /* ... */ }
    }
    

    现在可以用 PHP 原生语法来使用结构化的元数据,而非 PHPDoc 声明。

    <?php
    interface ActionHandler
    {
        public function execute();
    }
    
    #[Attribute]
    class SetUp {}
    
    class CopyFile implements ActionHandler
    {
        public string $fileName;
        public string $targetDirectory;
    
        #[SetUp]
        public function fileExists()
        {
            if (!file_exists($this->fileName)) {
                throw new RuntimeException("File does not exist");
            }
        }
    
        #[SetUp]
        public function targetDirectoryExists()
        {
            if (!file_exists($this->targetDirectory)) {
                mkdir($this->targetDirectory);
            } elseif (!is_dir($this->targetDirectory)) {
                throw new RuntimeException("Target directory $this->targetDirectory is not a directory");
            }
        }
    
        public function execute()
        {
            copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName));
        }
    }
    
    function executeAction(ActionHandler $actionHandler)
    {
        $reflection = new ReflectionObject($actionHandler);
    
        foreach ($reflection->getMethods() as $method) {
            $attributes = $method->getAttributes(SetUp::class);
    
            if (count($attributes) > 0) {
                $methodName = $method->getName();
    
                $actionHandler->$methodName();
            }
        }
    
        $actionHandler->execute();
    }
    
    $copyAction = new CopyFile();
    $copyAction->fileName = "/tmp/foo.jpg";
    $copyAction->targetDirectory = "/home/user";
    
    executeAction($copyAction);