22
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ウコンの力でsetter/getter生やし放題

Last updated at Posted at 2015-10-14

turmeric-spice というphpでオブジェクト作成をサポートするライブラリを作ったので書きます。このライブラリは MagicSpice という先達のライブラリにインスパイアされたものです。

基本的な使い方

packagist からインストールできます

composer require gong023/turmeric-spice

以下のようなクラス定義を行います。

use TurmericSpice\ReadableAttributes;

class User
{
    use ReadableAttributes {
        mayHaveAsInt     as public getId;
        mayHaveAsString  as public getName;
        mayHaveAsFloat   as public getBalance;
        mayHaveAsBoolean as public getRestricted;
        mayHaveAsArray   as public getFriendIds;
    }
}

このような定義をすると、以下の getter メソッドを使うことができます。なお、これらの getter メソッドはIDEの補完でちゃんと見ることができます。

$user = new User([
    'id'         => 1,
    'name'       => 'Taro',
    'balance'    => 100.0,
    'restricted' => false,
    'friend_ids' => [2, 3, 4],
]);

$user->getId();        // 1
$user->getName();      // 'Taro'
$user->getBalance();   // 100.0
$user->getRestricted;  // false
$user->getFriendIds(); // [2, 3, 4]
$user->toArray();      // コンストラクタに渡した時のような配列を返します

setter を使いたいときは、TurmericSpice\ReadWriteAttributes トレイトを使用します。

use TurmericSpice\ReadWriteAttributes;

class User
{
    use ReadWriteAttributes {
        setValue         as public setId;
        setValue         as public setName;
        mayHaveAsInt     as public getId;
        mustHaveAsString as public getName;
    }
}

$user = new User(['id' => null]);
$user->setId(1);
$user->getId(); // 1
$user->setName('Taro');
$user->getName(); // 'Taro'

may / must DSL

turmeric-spice は maymust という二種類のDSLを持っています。
どちらも getter メソッド呼び出し時に適切な型へのキャストを行ってくれますが、キーが未定義だった場合や値が null だった場合の挙動が変化します。may は何も言わずにキャストし、must\TurmericSpice\Container\InvalidAttributeException という例外を投げます。

use TurmericSpice\ReadableAttributes;

class User
{
    use ReadableAttributes {
        mayHaveAsString   as public getName;
        mayHaveAsFloat    as public getBalance;
        mustHaveAsInt     as public getId;
        mustHaveAsArray   as public getFriendIds;
        mustHaveAsBoolean as public getRestricted;
    }
}

$user = new User([
    'name'       => null,
    'balance'    => 100,
    'id'         => null,
    'restricted' => 'false',
]);

$user->getName();       // null が与えられていますがキャストされ '' が返ります。
$user->getBalance();    // int の 100 が与えられていますがキャストされ 100.0 が返ります。
$user->getId();         // null が与えられているため InvalidAttributeException が投げられます。
$user->getFriendIds();  // キーが未定義のため InvalidAttributeException が投げられます。
$user->getRestricted(); // 'false' が与えられていますが false が返ります。

発展的な使い方

turmeric-spice のトレイトをインポートすると attributes というプロパティが付き、それに直接アクセスすることでより多彩な挙動を使えます。

use TurmericSpice\ReadableAttributes;

class User
{
    use ReadableAttributes;

    public function getNameTaro()
    {
        $validate = function ($value) { return $value === 'Taro'; };

        return $this->attributes->mustHave('name')->asString($validate);
    }

    public function getCreated()
    {
        return $this->attributes->mustHave('created')->asInstanceOf('\Datetime');
    }

    public function getUpdated()
    {
        return $this->attributes->mayHave('updated')->asInstanceOf('\Datetime');
    }

    public function getUpdatedRaw()
    {
        return $this->attributes->mayHave('updated')->value();
    }

    public function getRawArray()
    {
        return $this->attributes->getRaw();
    }
}

$user = new User([
    'name'    => 'Jiro',
    'created' => '2015-01-01 00:00:00',
    'updated' => '2015-01-01 00:00:00',
]);

$user->getNameTaro();   // バリデーションに引っかかるので InvalidAttributeException が投げられます。
$user->getCreated();    // \Datetime のインスタンスではないので InvalidAttributeException が投げられます。
$user->getUpdated();    // new \Datetime('2015-01-01 00:00:00'); が返ります
$user->getUpdatedRaw(); // '2015-01-01 00:00:00' が返ります
$user->getRawArray();   // コンストラクタに渡した配列が返ります。toArray とは違い値のキャストなどは行われません。

version 0.2.0 以降では int[], float[], string[], bool[], YourClass[] も指定できるようになっています

use TurmericSpice\ReadableAttributes;

class User
{
    use ReadableAttributes {
        mayHaveAsIntArray    as public getFriendIds;
        mayHaveStringArray   as public getFriendNames;
        mustHaveAsFloatArray as public getBalanceHistories;
    }

    public function getUpdatedHistories()
    {
        return $this->attributes->mayHave('updated_histories')->asInstanceArray('\\Datetime');
    }
}

$user = new User([
    'friend_ids'        => ['1', '2', '3'],
    'friend_names'      => ['name1', 'name2', null],
    'balance_histories' => [10.0, 20.0, null],
    'updated_histories' => ['2015-01-01 00:00:00', '2016-01-01 00:00:00'],
]);

$user->getFriendIds;          // int にキャストされるので [1, 2, 3] が返ります
$user->getFriendNames;        // null が含まれていますが ['name1', 'name2', ''] が返ります
$user->balanceHistories();    // null が含まれているので exception が投げられます
$user->getUpdatedHistories(); // \Datetime にキャストされるので [new Datetime('2015-01-01 00:00:00'), new Datetime('2016-01-01 00:00:00')] が返ります

混みいった型周りの挙動とかを確かめるには実装みてもらうのがいいと思います。

ちなみに、MagicSpice は関数呼び出しを極力減らそうという意図が感じられるのに対し、turmeric-spice はその点でそこまでストイックに実装していません。では遅いかというとそんなことはない気がしていて、一番ボトルネックになりそうなこの辺のキャッシュ はしているし、MagicSpice では関数呼び出しが少ない代わりに Reflection class が使われていたりするので、まあパフォーマンスはちゃんと測ってみないとなんとも言えないなと思っています。
あと名前の由来はメタプログラミングっぽいところがあるので語呂をとって turmeric、MagiSpice からインスパイアされたものなので spice です。

22
23
2

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
22
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?