ポンコツエンジニアのごじゃっぺ開発日記。

いろいろポンコツだけど、気にするな。エンジニアの日々の開発などの記録を残していきます。 自動で収入を得られるサービスやシステムを作ることが目標!!

【PHP】Reflectionを使わずにprivateなプロパティやメソッドにアクセスする【Closure::bind】

プログラミングを普通にしていたらprivateprotectedなプロパティやメソッドにアクセスすることはないかもしれません。

しかし、ユニットテストでprivateなプロパティの値を設定しておいてテストを実行する、みたいなことをしたくなることがあるかもしれません。また、プライベートなメソッドもテストを書きたいかもしれません。

そんなときにどのように書くのでしょうか。Reflectionを使うという方法もあるかと思います。この記事では別の方法を紹介したいと思います。

プライベートメソッドはテストを書くべきか

プライベートメソッドのテストを書くべきか、という議論はあると思うので、今回の記事ではプライベートメソッドのテストも書きたいということにしておきましょう。書くべきなのかという話についてはt-wadaさんの以下の記事が参考になるかなと思います。

t-wada.hatenablog.jp

自分もPHPでPHPUnitを使ってテストを書いていたのですが、privateなプロパティを事前に操作したり、テストを実行した結果プロパティがどう変わっているかを確認したり、プライベートなメソッドもテストしたくなったりしました。

そんなときにReflectionを使わずに別の方法でやりました、という紹介になります。

Closure::bindとは

Closure::bindというのを利用しました。

www.php.net

ドキュメントにはこのように書かれています。

バインドされたオブジェクトとクラスのスコープでクロージャを複製する

public static Closure::bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure

引数は以下の3つです。

パラメータ 説明
Closure $closure バインドする無名関数。
?object $newThis 指定した無名関数をバインドするオブジェクト。クロージャのバインドを解除するには null を指定します。
object string|null $newScope|クロージャを関連づけるクラススコープ、あるいは 'static' で現在のスコープを維持します。 オブジェクトを渡した場合は、そのオブジェクトの型をその代わりに使います。 これは、バインドしたオブジェクトの protected メソッドや private メソッドのアクセス権を決めます。 このパラメータに、内部クラスのオブジェクトを渡すことはできません。

戻り値には、新しい Closure オブジェクトを返します。 失敗した場合は null を返します。

使い方としては、バインドしたいオブジェクトを第2引数で渡しつつ、第3引数でそのオブジェクトのスコープを渡す。そのオブジェクトに対して実行したい無名関数を第1引数に指定する感じでしょうか。

実際にプログラムを書いてアクセスしてみる

サンプルとして、以下のような犬クラスを作成してみました。

<?php

class Dog {
    public function __construct(
        private string $name,
    ) {
    }

    private function getName(): string {
        return $this->name;
    }

    private function setName($name): void {
        $this->name = $name;
    }
}

$pochi = new Dog('ポチ');

単純に__construct()で名前を渡して、getName()で取得するだけです。

また、PHP8.0から使えるようになったコンストラクタのプロモーションを利用しています。

PHP 8.0.0 以降では、コンストラクタの引数を 対応するオブジェクトのプロパティに昇格させることができます。 コンストラクタの引数をプロパティに代入し、それ以外の操作を行わないことはよくあることです。 コンストラクタのプロモーションは、こういった場合の短縮記法を提供します。

www.php.net

しかし、このクラスの良くないところとして、getName()setName()がprivateに設定されているので、外から呼ぶことができません。こんな実装をする人はいないとは思うのですが、サンプルなのでね。

privateなプロパティにアクセスする

まずは、privateで宣言されているクラスプロパティの$nameにアクセスしてみたいなと思います。

<?php

$name = Closure::bind(
    fn () => $this->name,
    $pochi,
    $pochi::class
)();

echo $name . "\n";

実行結果

ポチ

このように、Closure::bindを利用してprivateで宣言されている$nameの値を取得することができました。

もちろん、値の変更も可能です。

Closure::bind(
    fn ($name) => $this->name = $name,
    $pochi,
    $pochi::class
)('チョコ');

$name = Closure::bind(
    fn () => $this->name,
    $pochi,
    $pochi::class
)();

echo $name . "\n";

実行結果

チョコ

このように、引数で渡しつつ、値を上書くことができます。

privateなメソッドを呼ぶ

次にprivateで宣言されたgetName()を呼んでみたいと思います。

$name = Closure::bind(
    fn () => $this->getName(),
    $pochi,
    $pochi::class
)();

echo $name . "\n";

実行結果

チョコ

このように、getName()を呼び、戻り値を受け取ることができました。

また、メソッドの引数に値を渡すこともできます。

Closure::bind(
    fn ($name) => $this->setName($name),
    $pochi,
    $pochi::class
)('シロ');

$name = Closure::bind(
    fn () => $this->getName(),
    $pochi,
    $pochi::class
)();

echo $name . "\n";

実行結果

シロ

さいごに

このようにClosure::bindを利用することで、プライベートなメソッドやプロパティにアクセスすることができました。Reflectionを使うよりシンプルにかけるので個人的には好きかもしれないです。

お問い合わせプライバシーポリシー制作物