迁移到 PHP 8.0:使用 $this 时取消绑定 $this

Migrate to PHP 8.0: Unbinding $this when $this is used

一步一步,我正在将我的项目从 PHP 7.1 迁移到 PHP 8.0.

在官方 PHP 手册中,“已弃用的功能” 一章的子章节 “从 PHP 7 迁移。 3.x to PHP 7.4.x",我试着理解了下面的deprecation description:

Unbinding $this when $this is used

Unbinding $this of a non-static closure that uses $this is deprecated.

虽然,没有成功。

这就是为什么我很感激,如果有人能详细解释我的意思,这是什么意思。也许代码片段也有帮助。

非常感谢您的宝贵时间!


也许这对解释也有帮助:

在我的项目中,我认为只有一种情况可以适用此弃用通知:下面介绍的 RouteCollection class 的方法 executeGroupHandler .我更愿意粘贴更多 class 的代码,以帮助您理解我使用方法 executeGroupHandler.

的上下文

路线集合class:

<?php

namespace Packages\Router;

//...
use Packages\Router\RouteCollectionInterface;


/**
 * Route collection.
 */
class RouteCollection implements RouteCollectionInterface {

    //...

    /**
     * Group patterns list. Indexed array.
     *
     * Each time a group handler is executed its pattern is saved in this list.
     * All addRoute operations taken place inside the scope of a group handler
     * prefix the pattern of the corresponding route with the saved group pattern.
     *
     * @var array
     */
    private $groupPatterns = [];

    //...

    /**
     * Add a group (helper method).
     *
     * @param string $pattern Group pattern.
     * @param \Closure $handler Group handler.
     * @return $this
     */
    public function group(string $pattern, \Closure $handler) {
        $this->addGroup($pattern, $handler);
        return $this;
    }

    /**
     * Add a group.
     *
     * @param string $pattern Group pattern.
     * @param \Closure $handler Group handler.
     * @return $this
     */
    private function addGroup(string $pattern, \Closure $handler) {
        $this->saveGroupPattern($pattern);

        $this->executeGroupHandler($handler);

        /*
         * Remove the last group pattern from the group patterns list. This step
         * is performed only after all calls for adding groups/routes inside the
         * scope of the current group handler have finished their processing.
         */
        $this->popLastGroupPattern();

        return $this;
    }

    /**
     * Save a group pattern.
     *
     * @param string $pattern Group pattern.
     * @return $this
     */
    private function saveGroupPattern(string $pattern) {
        $this->groupPatterns[] = $pattern;
        return $this;
    }

    /**
     * Execute a group handler.
     *
     * Temporarily bind the group handler to the route collection 
     * object - defined by the argument in Closure::call - and 
     * execute it. Inside the scope of the group handler, the route 
     * collection will be accessed using the keyword "$this".
     * 
     * @link https://www.php.net/manual/en/closure.call.php Closure::call
     *
     * @param \Closure $handler Group handler.
     * @return mixed The return value of calling the handler.
     */
    private function executeGroupHandler(\Closure $handler) {
        return $handler->call($this);
    }

    /**
     * Pop the group pattern off the end of group patterns list.
     *
     * @return string The popped group pattern.
     */
    private function popLastGroupPattern() {
        return array_pop($this->groupPatterns);
    }

}

RouteCollection的使用class:

定义了 RouteCollection class 后,我使用它类似于以下内容:

<?php

use Packages\Router\RouteCollection;
use SampleMvc\App\View\Template\Users\AddUser as AddUserView;
use SampleMvc\App\Controller\Users\AddUser as AddUserController;

$routeCollection = new RouteCollection();

// Add a group of routes to the route collection.
$routeCollection->group('/users/add', function() {
    $this->get('', [AddUserView::class, 'index']);

    $this->post('', [
        'controller' => AddUserController::class,
        'view' => [AddUserView::class, 'addUser'],
    ]);
});

//...

问得好,我也不明白 PHP 中的“闭包的解除绑定变量”是什么意思。

我写了一个简单的测试来检查绑定另一个 $this 是否可以被视为“解除绑定 $this”:

class ClosureTest extends TestCase
{

    private $x = 1;

    public function testClosure(): void
    {
        var_dump(\PHP_VERSION);
        $callable = function (): int {
            return $this->x;
        };
        self::assertSame(1, \call_user_func($callable));

        $closure = \Closure::fromCallable($callable);

        $obj = new class {
            private $x = 2;
        };
        self::assertSame(2, $closure->call($obj));
    }
}

我已经 运行 它在 7.3 和 8.0 下,但没有收到任何通知,所以可能您的代码在 8.0 下 运行 也是安全的。但我会进一步调查这个问题。

弃用是 proposed (and accepted) in this RFC,它提供了有关弃用内容和原因的更多详细信息。

最后一句解释了哪些闭包受到影响:

In particular this applies to non-static closures declared inside non-static methods. A $this binding can be avoided in the first place by marking the closure as static.

这后来进一步缩小 in this commit,因此它仅适用于闭包中实际提到 $this 的闭包。

同时,第一句更清楚地说明了 什么 被弃用:

Currently it is possible to unbind the $this variable from a closure that originally had one by using $closure->bindTo(null).

关键词是unbind而不是rebind,例子中的null

尼基塔有更多背景资料 in this comment:

The reason we're interested in this deprecation is exclusively to enable some performance improvements in PHP 8 related to $this accesses, which is enabled by the removal of static calls to non-static methods. $this accesses can be split into two categories: Those where we know $this to be non-null and those where we don't. Method calls (will) fall into the former category. With this deprecation closures will also fall into the former category.

换句话说,在 PHP 8 中,引擎会盲目地假设闭包中对 $this 的任何引用实际上都是一个对象,而不是 null。

所以被弃用的特定场景是你有一个提到 $this 的闭包,然后你 取消绑定 它,所以 $this 不是'根本没有设置任何东西。只要您为 $this 提供新值,您应该不会受到影响,因为 $this 永远不会是 null.