ryunosuke/utility-attribute

v1.0.2 2024-03-23 06:59 UTC

This package is auto-updated.

Last update: 2024-04-23 07:16:40 UTC


README

Description

This package provides generic/useful attribute.

Install

{
  "require": {
    "ryunosuke/utility-attribute": "*"
  }
}

Usage

see below.

<?php
require_once __DIR__ . '/vendor/autoload.php';

use ryunosuke\utility\attribute\Attribute\AbstractAttribute;
use ryunosuke\utility\attribute\Attribute\DebugInfo;
use ryunosuke\utility\attribute\Attribute\Friend;
use ryunosuke\utility\attribute\Attribute\Json;
use ryunosuke\utility\attribute\ClassTrait\DebugInfoTrait;
use ryunosuke\utility\attribute\ClassTrait\FriendTrait;
use ryunosuke\utility\attribute\ClassTrait\JsonTrait;
use ryunosuke\utility\attribute\ReflectionAttribute;

#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
class SampleAttribute1 extends AbstractAttribute
{
    public function __construct(int $a, $b = 2, $c = 3) { }
}

#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
class SampleAttribute2 extends AbstractAttribute
{
}

/**
 * below comment is autogenerated by annotate.
 *
 * @auto-document-Friend:begin
 * @property int $privateField
 * @auto-document-Friend:end
 */
#[SampleAttribute1(1, c: 9)]
class SampleClass implements JsonSerializable
{
    use DebugInfoTrait;
    use FriendTrait;
    use JsonTrait;

    #[Friend]
    #[DebugInfo(false), Json(true)]
    private int $privateField = 123;

    #[Json(false)]
    public int $publicField = 456;

    #[SampleAttribute1(1)]
    public function method()
    {
    }
}

#[SampleAttribute2]
class SampleSubClass extends SampleClass
{
    public function method() { return parent::method(); }
}

$sample    = new SampleClass();
$subsample = new SampleSubClass();

# Get attribute by following inheritance tree
var_dump(SampleAttribute1::of($subsample->method(...), 0));
/*= NULL */
var_dump(SampleAttribute1::of($subsample->method(...), ReflectionAttribute::FOLLOW_INHERITANCE));
/*= object(ryunosuke\utility\attribute\ReflectionAttribute)#12 (1) {
  ["SampleAttribute1"]=>
  array(3) {
    ["a"]=>
    int(1)
    ["b"]=>
    int(2)
    ["c"]=>
    int(3)
  }
} */

# Get attribute by seeing class also
var_dump(SampleAttribute2::of($subsample->method(...), 0));
/*= NULL */
var_dump(SampleAttribute2::of($subsample->method(...), ReflectionAttribute::SEE_ALSO_CLASS));
/*= object(ryunosuke\utility\attribute\ReflectionAttribute)#13 (1) {
  ["SampleAttribute2"]=>
  array(0) {
  }
} */

# Get attribute by following inheritance tree & seeing class also & merge repeatable
var_dump(SampleAttribute1::arrayOf($sample->method(...), ReflectionAttribute::ALL));
/*= array(2) {
  [0]=>
  object(ryunosuke\utility\attribute\ReflectionAttribute)#10 (1) {
    ["SampleAttribute1"]=>
    array(3) {
      ["a"]=>
      int(1)
      ["b"]=>
      int(2)
      ["c"]=>
      int(3)
    }
  }
  [1]=>
  object(ryunosuke\utility\attribute\ReflectionAttribute)#11 (1) {
    ["SampleAttribute1"]=>
    array(3) {
      ["a"]=>
      int(1)
      ["b"]=>
      int(2)
      ["c"]=>
      int(9)
    }
  }
} */

# Get attribute without Reflection
var_dump(SampleAttribute1::arrayOf($sample));
/*= array(1) {
  [0]=>
  object(ryunosuke\utility\attribute\ReflectionAttribute)#12 (1) {
    ["SampleAttribute1"]=>
    array(3) {
      ["a"]=>
      int(1)
      ["b"]=>
      int(2)
      ["c"]=>
      int(9)
    }
  }
} */
# Single version above
$attr = SampleAttribute1::of($sample);

# ReflectionAttribute implements getReflection
var_dump($attr->getReflection());
/*= object(ReflectionObject)#12 (1) {
  ["name"]=>
  string(11) "SampleClass"
} */

# ReflectionAttribute implements getNamedArguments
var_dump($attr->getNamedArguments());
/*= array(3) {
  ["a"]=>
  int(1)
  ["b"]=>
  int(2)
  ["c"]=>
  int(9)
} */
# Compare getArguments and above
var_dump($attr->getArguments());
/*= array(2) {
  [0]=>
  int(1)
  ["c"]=>
  int(9)
} */

# ReflectionAttribute implements getNamedArgument
var_dump($attr->getNamedArgument('c'));
/*= int(9) */
# Same as
$params = $attr->getArguments();
var_dump(array_key_exists(2, $params) ? $params[2] : (array_key_exists('c', $params) ? $params['c'] : null));
/*= int(9) */
# But native doesn't do this
var_dump($attr->getNamedArgument('b'));
/*= int(2) */

# Access private field by Friend
var_dump($sample->privateField);
/*= int(123) */
var_dump($subsample->privateField);
/*= int(123) */

# $privateField is invisible at var_dump by DebugInfo
var_dump($sample);
/*= object(SampleClass)#5 (1) {
  ["publicField"]=>
  int(456)
} */

# $privateField is visible/$publicField is invisible at json_encode by Json
var_dump(json_encode($sample, JSON_PRETTY_PRINT));
/*= string(27) "{
    "privateField": 123
}" */

# Rewrite SampleClass DocComment
$sample->annotate();
?>
member native utility
const by class-string (new ReflectionClassConstant(ClassName::class, 'CONST_NAME'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of(ClassName::class . '::CONST_NAME')
property by class-string (new ReflectionProperty(ClassName::class, 'field_name'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of(ClassName::class . '::$field_name')
method by class-string (new ReflectionMethod(ClassName::class, 'method_name'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of(ClassName::class . '::method_name')
const by $object (new ReflectionClassConstant($object, 'CONST_NAME'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of([$object, 'CONST_NAME'])
property by $object (new ReflectionProperty($object, 'field_name'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of([$object, '$field_name'])
method by $object (new ReflectionMethod($object, 'method_name'))->getAttributes(AttributeClass::class)[0] ?? null AttributeClass::of($object->method(...))

Note

個人的に php 標準の属性取得は下記の点で使いにくいです。

  • わざわざリフレクションを経由したくない
    • 手元にある class-string やインスタンスでダイレクトに取得したい
  • name 未指定で取得することはまずない
    • それなら Attribute クラスを主体にして取れた方が便利
  • repeat な属性より single な属性の方が用途が多い
    • それなら配列で返さず、?Attribute で返ってくる方が使いやすい
  • getArguments が本当に arguments を返すので使いづらい
    • 上記のように妙な分岐を入れざるを得ないので名前付き引数で統一して取得したい
  • クラスの属性をメンバーの属性のデフォルトとして使いたい
    • 例えば PHPUnit の @group とかそういうやつ。シチュエーションは結構多い
  • メソッドを委譲するだけで属性が消えてしまう
    • 完全上書きなら元のメソッドの属性が欲しいことが多い

Release

Versioning is romantic versioning(no semantic versioning).

  • major: large BC break. e.g. change architecture, package, class etc
  • minor: small BC break. e.g. change arguments, return type etc
  • patch: no BC break. e.g. fix bug, add optional arguments, code format etc

1.0.2

  • [feature] MERGE_REPEATABLE を追加

1.0.1

  • fix demo

1.0.0

  • publish

License

MIT