【PHP8.x】bindToメソッドの使い方

bindToメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

bindToメソッドは、既存のクロージャ(無名関数)の$thisオブジェクトとスコープを変更した新しいクロージャを生成して返すメソッドです。

PHPのクロージャは、定義された時点での$thisオブジェクトと、そのクロージャがアクセスできるメンバー(プロパティやメソッド)の範囲(スコープ)を保持します。しかし、プログラムの状況によっては、この$thisが指すオブジェクトを別のインスタンスに変更したり、クロージャがアクセスできるprotectedprivateメンバーの範囲(スコープ)を別のクラスのものに変更したい場合があります。

bindToメソッドは、元のクロージャのロジックはそのままに、これらのコンテキストだけを変更した「新しい」クロージャを作成します。元のクロージャ自体は変更されません。

第一引数には、新しいクロージャ内で$thisとして扱いたいオブジェクトを指定します。もしnullを指定した場合、新しいクロージャは$thisを持たない静的なクロージャとして振る舞います。

第二引数には、新しいクロージャがアクセスを許可されるスコープを定義するクラス名を文字列で指定するか、そのクラスのオブジェクトを指定します。これにより、新しいクロージャはその指定されたクラスのprotectedprivateメンバーにアクセスできるようになります。staticという文字列を指定すると、現在のスコープが維持されます。

このメソッドは、変更された$thisとスコープを持つ新しいClosureオブジェクトを返します。これにより、既存のコードを変更することなく、異なるオブジェクトのコンテキストで同じ処理を実行したり、特定のクラスの内部ロジックにクロージャを「注入」したりといった高度な制御が可能になります。

構文(syntax)

1<?php
2
3$closure = function () {
4    return $this->name ?? 'No name';
5};
6
7class MyClass {
8    public string $name = 'Alice';
9}
10
11$object = new MyClass();
12$newClosure = $closure->bindTo($object, MyClass::class);
13
14?>

引数(parameters)

?object $newThis, object|string|null $newScope = 'static'

  • ?object $newThis: $thisとしてバインドするオブジェクト。nullを指定すると、バインドしない。
  • object|string|null $newScope = 'static': $thisのスコープを定義するオブジェクト、またはスコープとして使用するクラス名。'static'を指定すると、静的スコープとなる。

戻り値(return)

?Closure

指定された $newscope オブジェクトのコンテキストで、元のクロージャオブジェクトへの参照を返します。 このメソッドは、クロージャの $this を特定の値にバインドし、必要に応じて静的スコープも変更します。

サンプルコード

PHP Closure::bindTo で $this を変更する

1<?php
2
3class PlayerCharacter
4{
5    private string $name;
6
7    public function __construct(string $name)
8    {
9        $this->name = $name;
10    }
11
12    /**
13     * このPlayerCharacterインスタンスの$thisにバインドされたクロージャを返します。
14     * クロージャ内では$this->nameにアクセスできます。
15     */
16    public function getInfoClosure(): Closure
17    {
18        return function () {
19            return "プレイヤー名: " . $this->name;
20        };
21    }
22}
23
24class NonPlayerCharacter
25{
26    private string $name;
27    private int $level;
28
29    public function __construct(string $name, int $level)
30    {
31        $this->name = $name;
32        $this->level = $level;
33    }
34
35    public function getLevel(): int
36    {
37        return $this->level;
38    }
39}
40
41// 1. PlayerCharacterインスタンスを作成し、そこからクロージャを取得します。
42$hero = new PlayerCharacter("勇者アルス");
43$heroInfoClosure = $hero->getInfoClosure();
44
45echo "--- 元のクロージャの実行 (PlayerCharacter) ---\n";
46// クロージャは$heroオブジェクトにバインドされているため、$this->nameは"勇者アルス"になります。
47echo $heroInfoClosure() . "\n\n";
48
49// 2. NonPlayerCharacterインスタンスを作成します。
50$goblin = new NonPlayerCharacter("ゴブリン", 5);
51
52// 3. Closure::bindTo を使用して、クロージャの $this (参照元オブジェクト) とスコープを変更します。
53//    - 第一引数 ($newThis): 新しい$thisとなるオブジェクトを指定します。ここでは$goblin。
54//    - 第二引数 ($newScope): 新しいスコープを指定します。
55//      文字列でクラス名 ('NonPlayerCharacter') を指定することで、
56//      クロージャはそのクラスのprivate/protectedメンバーにアクセスできるようになります。
57//      オブジェクト ($goblin) を指定しても同じ効果が得られます。
58//    元のクロージャ ($heroInfoClosure) が参照していた $this->name は、
59//    新しいオブジェクト ($goblin) の $name プロパティを参照するようになります。
60//    NonPlayerCharacterクラスも $name プロパティを持つため、正しく動作します。
61$goblinInfoClosure = $heroInfoClosure->bindTo($goblin, 'NonPlayerCharacter');
62
63echo "--- bindTo 後のクロージャの実行 (NonPlayerCharacter) ---\n";
64// クロージャは$goblinオブジェクトにバインドされ、NonPlayerCharacterのスコープで実行されるため、
65// $this->nameは"ゴブリン"になります。
66echo $goblinInfoClosure() . "\n";
67
68// 4. bindTo の第一引数に null を渡すと、$this が存在しないクロージャになります。
69//    その場合、クロージャ内で $this を参照しようとするとエラーが発生します。
70$detachedClosure = $heroInfoClosure->bindTo(null);
71echo "\n--- \$this が null のクロージャの実行 ---\n";
72try {
73    echo $detachedClosure() . "\n";
74} catch (Error $e) {
75    echo "エラー: " . $e->getMessage() . "\n"; // 例: Using $this when not in object context
76}
77

PHPのClosure::bindToメソッドは、既存のクロージャが参照する$thisオブジェクトと、そのクロージャがアクセスできるスコープ(privateやprotectedメンバーなど)を動的に変更するために使用されます。

このメソッドの第一引数$newThisには、クロージャが新たに参照するオブジェクトを指定します。ここにnullを渡すと、クロージャはどのオブジェクトにもバインドされなくなり、クロージャ内で$thisを参照するとエラーが発生します。第二引数$newScopeには、クロージャが実行される際の新しいスコープを指定します。クラス名を示す文字列、またはそのクラスのオブジェクトを指定することで、クロージャはそのクラスのprivateやprotectedメンバーにアクセスできるようになります。このメソッドは、$newThis$newScopeが適用された新しいClosureオブジェクトを返します。

サンプルコードでは、まずPlayerCharacterオブジェクトにバインドされたクロージャを作成し、「勇者アルス」というプレイヤー名を表示しています。次に、NonPlayerCharacterオブジェクトの「ゴブリン」を作成し、既存のクロージャをbindToメソッドを使って「ゴブリン」オブジェクトにバインドし直しました。これにより、同じクロージャを実行しても、「ゴブリン」の$this->nameが参照され、「ゴブリン」という名前が表示されます。bindToの第二引数にクラス名'NonPlayerCharacter'を指定することで、クロージャはNonPlayerCharacterのprivateプロパティにもアクセス可能になっています。このようにbindToは、クロージャの実行コンテキストを柔軟に切り替える際に役立つ機能です。

Closure::bindToは、クロージャが参照する$thisオブジェクトと、アクセス可能なスコープを変更する機能です。第一引数で$thisとなるオブジェクトを指定しますが、nullを指定するとクロージャ内で$thisを参照できなくなり、呼び出し時にエラーが発生しますので注意が必要です。第二引数にクラス名を指定すると、そのクラスのprivateprotectedメンバーにもアクセスできるようになります。しかし、これはカプセル化を破る行為であり、予期せぬ副作用やセキュリティリスクにつながる可能性があるため、利用には細心の注意を払ってください。bindToメソッドは新しいクロージャを返すため、元のクロージャは変更されない点も覚えておきましょう。