PHPのオーバーロードの仕組みについて

採用はこちら

PHPのオーバーロードとは、アクセスできない、または存在しないプロパティやメソッドに対する操作を、マジックメソッドによって動的に処理する仕組みです。

JavaやC++などでは、同じ名前のメソッドを引数の型や数を変えて複数定義する仕組みをオーバーロードと呼びます。

しかし、PHPでいうオーバーロードは意味が異なります。

PHPでは、__get()__set()__call()などのマジックメソッドを利用して、通常では直接アクセスできないプロパティやメソッドへの操作を処理します。

そのため、PHPのオーバーロードを理解するときは、他のプログラミング言語におけるメソッドオーバーロードとは分けて考えることが重要です。

目次

PHPのオーバーロードで使われるマジックメソッド

PHPのオーバーロードでは、主に6つのマジックメソッドが使用されます。

マジックメソッド主な役割
__get()アクセスできないプロパティを読み取る
__set()アクセスできないプロパティに値を書き込む
__isset()isset()empty()による確認を処理する
__unset()unset()による削除を処理する
__call()アクセスできないインスタンスメソッドの呼び出しを処理する
__callStatic()アクセスできない静的メソッドの呼び出しを処理する

ここでいう「アクセスできない」には、単に存在しない場合だけではなく、現在のスコープからアクセスできないprivateprotectedのメンバーなども含まれます。

プロパティをオーバーロードする仕組み

PHPでは、__get()__set()__isset()__unset()を利用してプロパティへの操作を制御できます。

__get()でプロパティを取得する

__get()は、アクセスできない、または存在しないプロパティを読み取ろうとしたときに自動的に呼び出されます。

class User
{
    private array $data = [
        'name' => 'Tanaka',
        'age' => 30
    ];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

$user = new User();

echo $user->name;

このクラスには、公開プロパティとしてnameは定義されていません。

しかし、

$user->name

と記述すると、__get()が呼び出され、内部の$data配列から値を取得できます。

この例では、結果としてTanakaが表示されます。

__get()の基本的な形式は次のとおりです。

public function __get(string $name): mixed
{
    // 処理
}

$nameには、アクセスされたプロパティ名が渡されます。

__set()でプロパティに値を代入する

__set()は、アクセスできない、または存在しないプロパティへ値を代入したときに呼び出されます。

class User
{
    private array $data = [];

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

$user = new User();

$user->name = 'Suzuki';

echo $user->name;

この場合、

$user->name = 'Suzuki';

という処理を__set()が受け取り、内部の$data配列へ値を保存します。

__set()の基本的な形式は次のとおりです。

public function __set(string $name, mixed $value): void
{
    // 処理
}

$nameにはプロパティ名、$valueには代入された値が渡されます。

__isset()でプロパティの存在を確認する

__isset()は、アクセスできない、または存在しないプロパティに対してisset()empty()が使用された場合に呼び出されます。

class User
{
    private array $data = [
        'name' => 'Tanaka'
    ];

    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }
}

$user = new User();

var_dump(isset($user->name));

この例では、内部の$data配列にnameが存在するため、trueが返されます。

動的に管理しているプロパティの存在確認を制御したい場合に便利です。

__unset()でプロパティを削除する

__unset()は、アクセスできない、または存在しないプロパティに対してunset()が使用された場合に呼び出されます。

class User
{
    private array $data = [
        'name' => 'Tanaka'
    ];

    public function __unset(string $name): void
    {
        unset($this->data[$name]);
    }
}

$user = new User();

unset($user->name);

この場合、nameという公開プロパティが直接削除されるわけではありません。

__unset()によって内部配列の値を削除しています。

メソッドをオーバーロードする仕組み

PHPでは、__call()__callStatic()を利用することで、アクセスできないメソッドの呼び出しを動的に処理できます。

__call()でインスタンスメソッドを処理する

__call()は、アクセスできない、または存在しないインスタンスメソッドが呼び出された場合に実行されます。

class Calculator
{
    public function __call(string $name, array $arguments): mixed
    {
        echo "呼び出されたメソッド: {$name}";

        return null;
    }
}

$calculator = new Calculator();

$calculator->add(10, 20);

Calculatorクラスにはadd()というメソッドがありません。

そのため、add()を呼び出すと、__call()が処理を受け取ります。

基本的な形式は次のとおりです。

public function __call(string $name, array $arguments): mixed
{
    // 処理
}

$nameには呼び出されたメソッド名が入り、$argumentsには渡された引数が配列として格納されます。

たとえば、

$obj->test('PHP', 100, true);

と呼び出した場合、__call()には概念的に次のような情報が渡されます。

$name = 'test';

$arguments = [
    'PHP',
    100,
    true
];

__callStatic()で静的メソッドを処理する

__callStatic()は、アクセスできない、または存在しない静的メソッドが呼び出された場合に実行されます。

class Utility
{
    public static function __callStatic(
        string $name,
        array $arguments
    ): mixed {
        echo "静的メソッド {$name} が呼び出されました";

        return null;
    }
}

Utility::hello('PHP');

この例では、hello()という静的メソッドは定義されていません。

そのため、__callStatic()が呼び出されます。

基本的な形式は次のとおりです。

public static function __callStatic(
    string $name,
    array $arguments
): mixed {
    // 処理
}

__call()がインスタンスメソッド用であるのに対し、__callStatic()は静的メソッド用です。

PHPとJava・C++のオーバーロードの違い

PHPのオーバーロードを理解するうえで重要なのが、JavaやC++などとの違いです。

JavaやC++では同名メソッドを複数定義できる

JavaやC++などでは、引数の数や型が異なれば、同じ名前のメソッドを複数定義できます。

たとえば、Javaでは次のような書き方ができます。

void test(int value) {
}

void test(String value) {
}

呼び出すときに渡された引数の型によって、どちらのtest()を使用するかが決まります。

PHPでは同名メソッドを引数違いで複数定義できない

PHPでは、同じクラスの中に同名メソッドを引数の数や型だけ変えて複数宣言することはできません。

たとえば、次のような定義はできません。

class Sample
{
    public function test(int $value)
    {
    }

    public function test(string $value)
    {
    }
}

PHPでは同名メソッドの再定義となってしまいます。

したがって、PHP公式ドキュメントでいうオーバーロードは、JavaやC++のメソッドオーバーロードとは別の仕組みです。

PHPでオーバーロードに似た柔軟な処理を実現する方法

PHPではJavaやC++と同じ形式のメソッドオーバーロードはできません。

ただし、デフォルト引数や可変長引数を使うことで、呼び出し側から見て似た柔軟性を持たせることはできます。

デフォルト引数を利用する

デフォルト引数を利用すると、一部の引数を省略できるメソッドを作れます。

class Greeting
{
    public function hello(
        string $name,
        string $message = 'こんにちは'
    ): void {
        echo "{$message}、{$name}さん";
    }
}

次のように、引数の数を変えて呼び出せます。

$greeting = new Greeting();

$greeting->hello('田中');

$greeting->hello('田中', 'おはようございます');

これはメソッドオーバーロードそのものではありません。

しかし、ひとつのメソッドで複数の呼び出し方に対応できます。

可変長引数を利用する

可変長引数を利用すると、任意の数の引数を受け取れます。

class Calculator
{
    public function add(int ...$numbers): int
    {
        return array_sum($numbers);
    }
}

$calculator = new Calculator();

echo $calculator->add(1, 2);

echo $calculator->add(1, 2, 3, 4);

...$numbersによって、複数の引数を配列として受け取れます。

こちらも厳密にはメソッドオーバーロードではありませんが、引数の数に柔軟に対応する方法として利用できます。

PHPのオーバーロードが使われる場面

オーバーロードを利用すると、通常のプロパティやメソッドでは実現しにくい動的なインターフェースを構築できます。

動的なプロパティ管理

__get()__set()を組み合わせると、内部では配列としてデータを保持しながら、利用側からは通常のプロパティのように操作できます。

class DataObject
{
    private array $data = [];

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

利用側では次のように記述できます。

$data = new DataObject();

$data->title = 'PHP入門';

echo $data->title;

内部では配列を使用していますが、利用側から見ると普通のプロパティのように扱えます。

動的なメソッドAPI

__call()を使えば、呼び出されたメソッド名を解析して処理を切り替えることもできます。

たとえば、

$user->findByName('Tanaka');
$user->findByEmail('example@example.com');

のような呼び出しを、個別のメソッドとして定義せずに処理する設計が可能です。

public function __call(string $name, array $arguments): mixed
{
    if (str_starts_with($name, 'findBy')) {
        $field = substr($name, 6);

        // $fieldを利用した検索処理
    }

    return null;
}

このような仕組みは、動的なAPIを設計したい場合に利用できます。

PHPでオーバーロードを使うメリット

PHPのオーバーロードには、柔軟なクラス設計ができるというメリットがあります。

柔軟なインターフェースを作れる

すべてのプロパティやメソッドをあらかじめ明示的に定義しなくても、動的に処理できます。

データ構造が変化する可能性がある場合などには便利です。

内部構造を隠蔽できる

外部からは、

$user->name

という単純なプロパティアクセスに見えても、内部では配列や別のデータ構造を参照できます。

クラスの内部実装を利用側から隠しやすい点もメリットです。

動的なAPIを構築できる

__call()__callStatic()を利用すれば、メソッド名に応じて処理を切り替えられます。

うまく設計すれば、簡潔で使いやすいAPIを作れる場合があります。

PHPでオーバーロードを使う際の注意点

オーバーロードは便利な反面、多用するとコードが分かりにくくなることがあります。

コードだけでは動作を把握しにくくなる

たとえば、

$user->name

というコードだけを見ても、実際のnameプロパティが存在するのか、__get()によって処理されているのか判断しにくい場合があります。

明示的にプロパティを定義できる場合は、通常のプロパティを使ったほうが可読性や保守性が高くなることがあります。

タイプミスに気づきにくくなることがある

本来、

$user->username

と書くべきところを、

$user->usrname

と誤記した場合でも、__get()が存在すると処理が続いてしまう可能性があります。

存在しないプロパティについては例外を発生させる設計も有効です。

public function __get(string $name): mixed
{
    if (!array_key_exists($name, $this->data)) {
        throw new OutOfBoundsException(
            "存在しないプロパティです: {$name}"
        );
    }

    return $this->data[$name];
}

このようにしておけば、スペルミスなどを早い段階で発見しやすくなります。

IDEや静的解析との相性に注意する

__get()__call()によって動的に処理されるプロパティやメソッドは、ソースコード上に明示的な定義がありません。

そのため、IDEのコード補完や静的解析ツールが正確に認識できない場合があります。

PHPDocなどによって補助情報を与えることもできますが、必要以上に動的な設計にしないことも重要です。

動的プロパティとオーバーロードの違い

PHPのオーバーロードと動的プロパティは、似ているように見えて別の仕組みです。

動的プロパティとは

動的プロパティとは、クラスで宣言されていないプロパティを実行時に直接作成する仕組みです。

たとえば、次のようなコードです。

class User
{
}

$user = new User();

$user->name = 'Tanaka';

Userクラスではnameが宣言されていません。

その状態で値を代入すると、動的プロパティとして扱われます。

ただし、PHP 8.2以降では、一部の例外を除いて動的プロパティの作成は非推奨になっています。

そのため、新しくPHPコードを設計する場合には、宣言されていないプロパティを安易に作る方法は避けたほうがよいでしょう。

__set()を使う場合は動的プロパティとは異なる

一方、次のように__set()を定義した場合は意味が異なります。

class User
{
    private array $data = [];

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }
}

この状態で、

$user->name = 'Tanaka';

と記述すると、__set()が呼び出されます。

単純にnameという動的プロパティが作成される処理とは異なるため、両者を区別して理解することが重要です。

プロパティのオーバーロードはオブジェクトに対して使用する

__get()__set()__isset()__unset()によるプロパティのオーバーロードは、基本的にオブジェクトコンテキストで利用します。

たとえば、

$obj->name

のようなアクセスが対象です。

これらのプロパティ用マジックメソッドを静的プロパティのオーバーロード目的で利用することはできません。

静的メソッドの呼び出しを動的に処理したい場合は、__callStatic()を使用します。

マジックメソッドは適切な可視性とシグネチャで定義する

PHPのマジックメソッドを利用するときは、PHPが定めている可視性やシグネチャに従う必要があります。

__get()__set()__call()などは、通常publicとして定義します。

たとえば、

public function __get(string $name): mixed
{
    // 処理
}

のように記述します。

型宣言を行う場合も、PHPで定められたマジックメソッドのシグネチャと矛盾しないよう注意が必要です。

オーバーロードとオーバーライドの違い

PHPを学習していると、「オーバーロード」と「オーバーライド」を混同することがあります。

しかし、この2つはまったく異なる仕組みです。

オーバーロードとは

PHPにおけるオーバーロードとは、アクセスできない、または存在しないプロパティやメソッドへの操作をマジックメソッドで動的に処理する仕組みです。

代表的なマジックメソッドには、次のものがあります。

__get()
__set()
__isset()
__unset()
__call()
__callStatic()

オーバーライドとは

オーバーライドとは、親クラスで定義されたメソッドを、子クラス側で再定義することです。

class Animal
{
    public function speak(): void
    {
        echo 'Animal';
    }
}

class Dog extends Animal
{
    public function speak(): void
    {
        echo 'Dog';
    }
}

この例では、DogクラスがAnimalクラスのspeak()をオーバーライドしています。

両者の違いをまとめると、次のようになります。

用語PHPでの意味
オーバーロードアクセスできないプロパティやメソッドへの操作を動的に処理する
オーバーライド親クラスのメソッドを子クラスで再定義する

PHPのオーバーロードの仕組みを理解するポイント

PHPのオーバーロードを理解するときは、「同じ名前のメソッドを複数定義する機能」と考えないことが重要です。

PHPでは、主に次のような対応関係でマジックメソッドが利用されます。

$obj->property
        ↓
     __get()

$obj->property = $value
        ↓
     __set()

isset($obj->property)
        ↓
     __isset()

unset($obj->property)
        ↓
     __unset()

$obj->method()
        ↓
     __call()

ClassName::method()
        ↓
     __callStatic()

ただし、実際にPHPがコードをそのまま上記のメソッド呼び出しへ書き換えているわけではありません。

仕組みを理解するための概念的な対応関係として考えると分かりやすいでしょう。

PHPのオーバーロードは必要な場面に限定して使う

PHPのオーバーロードは、動的で柔軟なクラス設計を可能にする便利な機能です。

__get()__set()を利用すれば、内部データを通常のプロパティのように扱えます。

また、__call()__callStatic()を使えば、動的なメソッドAPIを構築することも可能です。

一方で、多用するとコードの動作が分かりにくくなったり、タイプミスを発見しにくくなったり、IDEや静的解析ツールによる補完が効きにくくなったりする場合があります。

そのため、通常のプロパティやメソッドで十分に表現できる場合は明示的に定義し、動的な処理が必要な場面でオーバーロードを利用するのが適切です。

特にPHPでは、他言語のメソッドオーバーロードとは意味が異なる点、PHP 8.2以降では動的プロパティの扱いにも注意が必要な点を理解しておくと、より安全で保守しやすいコードを書きやすくなります。

以上、PHPのオーバーロードの仕組みについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次