函数・クロージャの引数の個数を調べる

その函数は引数をいくつ要求するのか、つまり「函数の引数の数」のことを、一般的に<dfn>arity</dfn>([アリティ](http://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AA%E3%83%86%E3%82%A3))と呼びます。

これはJavaScriptやRubyでは比較的容易に取得できます。

```javascript
> function f (a) {}
> f.length
1
> function g (a, b) {}
> g.length
2
> function k (a, b, c) {}
> k.length
3

> (function(a){}).length
1
> (function(a,b){}).length
2
> (function(a,b,c){}).length
3
```

Rubyだとこんな感じ

```ruby
> lambda{|a|}.arity
=> 1
> lambda{|a, b|}.arity
=> 2
> lambda{|a, b, c|}.arity
=> 3

> def f (a); end
> method(:f).arity
=> 1
> def g (a, b); end
> method(:g).arity
=> 2
> def h (a, b, c); end
> method(:h).arity
=> 3
```

じゃあPHPは? 函数の内部からは実行時に受け取った個数を[`func_num_args`](http://www.php.net/manual/ja/function.func-num-args.php)で参照することはできますが、欲しいのはコレジャナイです。飽くまで **定義された仮引数の個数** を、 **実行することなく**知りたいのです。

じゃあPHPはどうすんのさ
---------------------

[リフレクション](http://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%95%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3_(%E6%83%85%E5%A0%B1%E5%B7%A5%E5%AD%A6))です… [PHPのリフレクション](http://www.php.net/manual/ja/book.reflection.php)を利用するのです…

### 函数

```php
<?php

$f = function($a, $b){};
$c = new ReflectionFunction($f);
var_dump($c->getNumberOfParameters());

$d = new ReflectionFunction("strcmp");
var_dump($d->getNumberOfParameters());
```

[ReflectionFunction](http://www.php.net/manual/ja/class.reflectionfunction.php)を利用します。

これで[ユーザー定義函数](http://jp1.php.net/manual/ja/functions.user-defined.php)も[ビルトイン函数](http://jp1.php.net/manual/ja/functions.internal.php)も[無名函数](http://php.net/manual/ja/functions.anonymous.php)もばっちりです。やったね。

### メソッド

```php
<?php

class Hoge
{
    public static function fuga ($a, $b)
    {
    	return "fuga";
    }

    public function piyo ($a, $b, $c)
    {
    	return "piyo";
    }
}

$c = new ReflectionClass("Hoge");
var_dump($c->getMethod("fuga")->getNumberOfParameters());
var_dump($c->getMethod("piyo")->getNumberOfParameters());

$d = new ReflectionClass(new Hoge);
var_dump($d->getMethod("fuga")->getNumberOfParameters());
var_dump($d->getMethod("piyo")->getNumberOfParameters());
```

[ReflectionObject](http://www.php.net/manual/ja/class.reflectionobject.php)と[ReflectionMethod](http://www.php.net/manual/ja/class.reflectionmethod.php)を利用します。

[ReflectionClass::__construct](http://www.php.net/manual/ja/reflectionclass.construct.php)に渡す値はインスタンスオブジェクトでもクラス名を表す文字列でも大丈夫です。

### まとめ

めんどくさいので[callback](http://www.php.net/manual/ja/language.types.callable.php)の形式をまとめてサポートする函数を書いた。

```php
<?php

/**
 * Returns an indication of the number of arguments accepted by a callable.
 *
 * @param callable $callable
 * @return int|null
 */
function arity (callable $callable)
{
    try {
        if (is_array($callable) || (is_string($callable) && strpos($callable, '::') !== false)) {
            list($class, $method) = is_string($callable) ? explode('::', $callable) : $callable;
            $reflection = (new ReflectionClass($class))->getMethod($method);
        } else {
            $reflection = new ReflectionFunction($callable);
        }
        return $reflection->getNumberOfParameters();
    } catch (Exception $e) {
        return null;
    }
}

test_arity('strpos', 3);
test_arity('hoge', 3);
test_arity('Datetime::createFromFormat', 3);
test_arity(function($a, $b, $c){}, 3);
test_arity(array(new DateTime, 'setTime'), 3);

function hoge($a, $b, $c) {}

function test_arity($value, $expected)
{
    $actual = arity($value);
    echo str_replace(PHP_EOL, '', var_export($value, true));
    echo ' is ' . ($actual !== $expected ? 'in' : '') . 'valid.' . PHP_EOL;
}
```

[煮るなり焼くなりお好きにどうぞ](http://www.kmonos.net/nysl/) or [You just DO WHAT THE FUCK YOU WANT TO](http://en.wikipedia.org/wiki/WTFPL).

追記
----

### 1

忘れてた。[PHP: ReflectionFunctionAbstract::getNumberOfParameters - Manual](http://www.php.net/manual/ja/reflectionfunctionabstract.getnumberofparameters.php)のほかにも[PHP: ReflectionFunctionAbstract::getNumberOfRequiredParameters - Manual](http://www.php.net/manual/ja/reflectionfunctionabstract.getnumberofrequiredparameters.php)とかあるんで、お好みに合せてどうぞ。

### 2

[タイプヒンティング](http://php.net/manual/ja/language.oop5.typehinting.php)の[callback](http://www.php.net/manual/ja/language.types.callable.php)が追加されたのは5.4なので、5.3では動きません(のでバージョン指定を5.3から5.4に変更)。 @t_cyrill ありがたうございます。