LoginSignup
4
4

More than 5 years have passed since last update.

PHP でコンストラクタやメソッドを名前付き引数っぽく呼べるようにするトレイト

Last updated at Posted at 2013-07-25

NamedParameteruse することでコンストラクタやメソッドが連想配列を使って名前付き引数っぽく呼べます。

new SomeClass( ...ordered... )   → SomeClass::create([ ...named... ])
SomeClass::func( ...ordered... ) → SomeClass::func_([ ...named... ])
<?php
trait NamedParameter
{
    private static function resolve(array $params, \ReflectionMethod $ref)
    {
        $args = [];

        /* @var $param \ReflectionParameter */
        foreach ($ref->getParameters() as $param)
        {
            $name = $param->getName();

            if (array_key_exists($name, $params))
            {
                $args[] = $params[$name];
            }
            else if ($param->isDefaultValueAvailable())
            {
                $args[] = $param->getDefaultValue();
            }
            else
            {
                throw new \InvalidArgumentException("Missing argument \"\${$name}\".");
            }
        }

        return $args;
    }

    public static function create(array $params)
    {
        $ref = new \ReflectionClass(get_called_class());

        $args = self::resolve($params, $ref->getConstructor());

        return $ref->newInstanceArgs($args);
    }

    public function __call($name, array $args)
    {
        $class = get_class($this);

        if (substr($name, -1) !== '_')
        {
            throw new \BadMethodCallException("Call to undefined method $class::$name()");
        }

        if (count($args) === 0)
        {
            throw new \InvalidArgumentException("Missing argument.");
        }

        $name = substr($name, 0, -1);

        try
        {
            $ref = new \ReflectionMethod($class, $name);
        }
        catch (\ReflectionException $ex)
        {
            throw new \BadMethodCallException("Call to undefined method $class::$name()");
        }

        $args = self::resolve($args[0], $ref);

        return $ref->invokeArgs($this, $args);
    }
}

class SomeClass
{
    use NamedParameter;

    public $args;

    public function __construct($aa, $bb = 123, $cc = "abc")
    {
        $this->args = func_get_args();
    }

    public function func($xx, $yy = 987, $zz = "xyz")
    {
        return func_get_args();
    }
}

class SomeClassTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    function constructor_ok()
    {
        // 普通の呼び出し
        // 引数 $bb を省略したいけど $cc は指定したいので仕方なく $bb も指定してる
        $obj1 = new SomeClass(true, 123, "hoge");

        // 名前付き引数っぽく呼び出す
        $obj2 = SomeClass::create(['aa' => true, 'cc' => "hoge"]);

        assertSame($obj1->args, $obj2->args);
    }

    /**
     * @test
     * @expectedException InvalidArgumentException
     * @expectedExceptionMessage $aa
     */
    function constructor_missing_arg()
    {
        // $aa は必須なので例外
        SomeClass::create(['bb' => 987, 'cc' => "hoge"]);
    }

    /**
     * @test
     */
    function method_ok()
    {
        $obj = new SomeClass(true);

        // 普通の呼び出し
        // 引数 $yy を省略したいけど $zz は指定したいので仕方なく $yy も指定してる
        $args1 = $obj->func(false, 987, "fuga");

        // 名前付き引数っぽく呼び出す
        $args2 = $obj->func_(['xx' => false, 'zz' => "fuga"]);

        assertSame($args1, $args2);
    }

    /**
     * @test
     * @expectedException BadMethodCallException
     * @expectedExceptionMessage SomeClass::undef()
     */
    function method_undef()
    {
        // 未定義なので例外
        $obj = new SomeClass(true);
        $obj->undef_([]);
    }
}
4
4
1

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
  3. You can use dark theme
What you can do with signing up
4
4