◆ メソッド用のオーバーロードできる仕組みです
◆ trait を use します 

JavaScript でオーバーロードするやつとほぼおなじ版の PHP 版オーバーロードは一応作っていましたが メソッドでやると一度無名関数通さないといけないとか面倒もところもあったのでメソッド用にしたものを作りました

static 変数でルールを持つ版

<?php

trait Overload{
static $overload_rule = [];
static $argtype_rule = [];

function __call($name, $args){
$rule = self::$overload_rule;
$argtype_rule = self::$argtype_rule;
if(array_key_exists($name, $rule)){
$matched_rule = $rule[$name];
foreach($matched_rule as $item){
foreach($item["type"] as $idx => $type){
if($idx === count($args)) continue 2;
if(isset($argtype_rule[$type]) && $argtype_rule[$type]($args[$idx])){
continue;
}else{
continue 2;
}
}
return [$this, $name . "__" . $item["name"]](...$args);
}
}
throw new Exception("method `${name}' not found");
}
}

class test{
use Overload;

function test__num($a){
return $a * $a;
}
function test__num2($a, $b){
return $a + $b;
}
function test__char($s){
return $s . $s;
}

}

test::$overload_rule = [
"test" => [
[
"type" => ["num", "num"],
"name" => "num2"
],[
"type" => ["num"],
"name" => "num"
],[
"type" => ["char"],
"name" => "char"
]
]
];
test::$argtype_rule = [
"num" => function($a){
return is_int($a) || is_float($a);
},
"char" => function($a){
return is_string($a) && strlen($a) === 1;
}
];

$t = new test();
var_dump($t->test("a"));
var_dump($t->test(123));
var_dump($t->test(123, 234));
try{
$t->test("ab");
}catch(Exception $e){
echo "Error: ", $e->getMessage();
}

ルール取得メソッドを作る版

<?php

trait Overload{

function __call($name, $args){
$rule = $this->getOverloadRule();
$overload_rule = $rule["base"];
$argtype_rule = $rule["condition"];
if(array_key_exists($name, $overload_rule)){
$matched_rule = $overload_rule[$name];
foreach($matched_rule as $item){
foreach($item["type"] as $idx => $type){
if($idx === count($args)) continue 2;
if(isset($argtype_rule[$type]) && $argtype_rule[$type]($args[$idx])){
continue;
}else{
continue 2;
}
}
return [$this, $name . "__" . $item["name"]](...$args);
}
}
throw new Exception("method `${name}' not found");
}

abstract function getOverloadRule();

}

class test{
use Overload;

function test__num($a){
return $a * $a;
}
function test__num2($a, $b){
return $a + $b;
}
function test__char($s){
return $s . $s;
}

function getOverloadRule(){
return [
"base" => [
"test" => [
[
"type" => ["num", "num"],
"name" => "num2"
],[
"type" => ["num"],
"name" => "num"
],[
"type" => ["char"],
"name" => "char"
]
]
],
"condition" => [
"num" => function($a){
return is_int($a) || is_float($a);
},
"char" => function($a){
return is_string($a) && strlen($a) === 1;
}
]
];
}

}

$t = new test();
var_dump($t->test("a"));
var_dump($t->test(123));
var_dump($t->test(123, 234));
try{
$t->test("ab");
}catch(Exception $e){
echo "Error: ", $e->getMessage();
}

結果

どっちもこんな結果です
string(2) "aa"
int(15129)
int(357)
Error: method `test' not found

(123*123=15129 です)

使い方

2 つバージョンありますが 基本一緒です
オーバーロードのルールを static 変数で保存しておくか 値は固定でメソッドから取得するかの違いです

メソッドからのほうが初期化不要で一般的かなとも思うのですが クラスの中が長くなって例のようにルールの量のほうが多いとかありそうなので 外に書ける方がいいのかなと static 変数版も用意してます


オーバーロードするメソッド名は

function [基準名]__[オーバーロード名](){}

となります
呼び出すときは基準名だけを使います

ルールで 引数に応じて オーバーロード名が付いたものを呼び出すようになっています

基本ルール

static 版の $overload_rule と メソッド版の base は一緒です

キーが基準名でバリューがその基準名のオーバーロードルールの連想配列です

オーバーロードのルールは引数条件とオーバーロード名の連想配列の配列です
配列の中から最初に引数条件がマッチした 1 つが使われます
配列の最初のほうが優先度高いです

引数条件は 文字列の配列です
オーバーロードする関数が呼び出された時の引数と引数条件の配列の並びが対応しています
文字列ごとに対応した関数を定義しておいて その関数に対応する引数を渡して 引数条件の配列すべてが true になったらマッチしたことになります

引数条件の配列を超える 呼び出し時の引数は無視されますが 引数条件に足りない場合はマッチしないものとなります

引数条件のルール

static 版の $argtype_rule と メソッド版の condition は一緒です

キー名が引数条件に指定される文字列で バリューが関数です
この関数の返り値が true だとマッチしたものになります

ここで定義していない文字列が引数条件に指定されるとマッチしないものとなります