search
LoginSignup
8

More than 5 years have passed since last update.

posted at

updated at

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

その函数は引数をいくつ要求するのか、つまり「函数の引数の数」のことを、一般的にarity(アリティ)と呼びます。

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

> 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だとこんな感じ

> 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で参照することはできますが、欲しいのはコレジャナイです。飽くまで 定義された仮引数の個数 を、 実行することなく知りたいのです。

じゃあPHPはどうすんのさ

リフレクションです… PHPのリフレクションを利用するのです…

函数

<?php

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

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

ReflectionFunctionを利用します。

これでユーザー定義函数ビルトイン函数無名函数もばっちりです。やったね。

メソッド

<?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());

ReflectionObjectReflectionMethodを利用します。

ReflectionClass::__constructに渡す値はインスタンスオブジェクトでもクラス名を表す文字列でも大丈夫です。

まとめ

めんどくさいのでcallbackの形式をまとめてサポートする函数を書いた。

<?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;
}

煮るなり焼くなりお好きにどうぞ or You just DO WHAT THE FUCK YOU WANT TO.

追記

1

忘れてた。PHP: ReflectionFunctionAbstract::getNumberOfParameters - ManualのほかにもPHP: ReflectionFunctionAbstract::getNumberOfRequiredParameters - Manualとかあるんで、お好みに合せてどうぞ。

2

タイプヒンティングcallbackが追加されたのは5.4なので、5.3では動きません(のでバージョン指定を5.3から5.4に変更)。 @t_cyrill ありがたうございます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8