Attributeのメモ

## 注意事項
PHP8上級・準上級試験の勉強途中で、学んだものなので、間違っているかもしれません。
ご理解よろしくおねがいします。

## Attributeとは
特定の引数を持っているメソッドがクラス内に定義されていた場合、リフレクションによってその定義内容ごとに**前処理**をすることができる。

### プロパティ
| 定数名                   | 説明                      | 付与できる場所        |
| --------------------- | ----------------------- | -------------- |
| TARGET_CLASS          | クラス<br>インターフェース<br>トレイト | クラス本体          |
| TARGET_FUNCTION       | 関数                      | グローバル関数        |
| TARGET_METHOD         | メソッド                    | クラスやトレイトのメソッド  |
| TARGET_PROPERTY       | プロパティ                   | クラスやトレイトのプロパティ |
| TARGET_CLASS_CONSTANT | クラス定数                   | クラス定数          |
| TARGET_PARAMETER      | パラメータ                   | 関数やメソッドの引数     |
| TARGET_ALL            | すべての場所                  | 全て             |

## サンプル

引用: https://www.php.net/manual/ja/language.attributes.overview.php
```php
<?php  
require __DIR__ . '/vendor/autoload.php';  
interface ActionHandler  
{  
    public function execute();  
}  
  
#[Attribute]  
class SetUp {}  
  
class CopyFile implements ActionHandler  
{  
    public string $fileName;  
    public string $targetDirectory;  
  
    #[SetUp]  
    public function fileExists()  
    {  
        if (!file_exists($this->fileName)) {  
            throw new RuntimeException("File does not exist");  
        }  
    }  
  
    #[SetUp]  
    public function targetDirectoryExists()  
    {  
        if (!file_exists($this->targetDirectory)) {  
            mkdir($this->targetDirectory);  
        } elseif (!is_dir($this->targetDirectory)) {  
            throw new RuntimeException("Target directory $this->targetDirectory is not a directory");  
        }  
    }  
    
  
    public function execute()  
    {  
        copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName));  
    }  
}  
  
function executeAction(ActionHandler $actionHandler)  
{  
    $reflection = new ReflectionObject($actionHandler);  
  
    foreach ($reflection->getMethods() as $method) {  
        $attributes = $method->getAttributes(SetUp::class);  
        echo print_r($attributes, true) . "\n";  
  
        if (count($attributes) > 0) {  
            $methodName = $method->getName();  
  
            $actionHandler->$methodName();  
  
        }  
    }  
  
    $actionHandler->execute();  
}  
  
$copyAction = new CopyFile();  
$copyAction->fileName = "/tmp/tmp.jpg";  
$copyAction->targetDirectory = "/home/locu";  
  
executeAction($copyAction);
```

**ActionHandler** (interface)
	executeメソッドを宣言

**Setup**(class)
	クラスに`#[Attribute]`を付与し、トリガーのような役割を担う

**CopyFile** (class)(実装 ActionHandler)
	`fileName` `targetDirectory` という変数を持ち、Attributeで`Setup`クラスを付与している
		fileExists
		targetDirectoryExists

`executeAction`(Method)
	リフレクションで、今回の場合executeメソッドを探して取得している
	取得したメソッドを自動実行`$method->getName();`
	最後に、executeを実行する

## 実装
#### index.php
```php
<?php  
  
use Framework\AppAttributeScanner;  
use Framework\AppLogger;  
use Framework\route\Route;  
  
require __DIR__ . '/../vendor/autoload.php';  
  
$logger = new AppLogger();  
// test  
AppLogger::debug('Index Initialize');  
  
// コントローラー内のAttributeが付与されているメソッドをすべて実行する  
AppAttributeScanner::findMethodsWithAttributeInPath("src/controllers");
```

### Route.php
```php
<?php  
  
namespace Framework\route;  
  
use Attribute;  
  
#[Attribute]  
class Route  
{  
  
    public function __construct(public string $method, public string $path)  
    {}  
  
}
```


### AppAttributeScanner.php
```php
<?php  
  
namespace Framework;  
  
use Framework\route\Route;  
  
class AppAttributeScanner  
{  
  
    /**  
     * 指定フォルダからすべてのクラスパスを取得する  
     * @param string $targetPath default: "src"  
     * @return array  
     */    public static function scan(string $targetPath = "src"): array  
    {  
  
        // `RecursiveIteratorIterator`: 再帰的なイテレーター処理  
        // `RecursiveDirectoryIterator`: ファイルシステムのディレクトリを再帰的に反復処理をするためのインターフェイス  
        // 指定フォルダ下のファイルのみを取得している  
        $files = new \RecursiveIteratorIterator(  
            new \RecursiveDirectoryIterator(  
                $targetPath,  
                \FilesystemIterator::SKIP_DOTS |            // .や..をスキップ  
                \FilesystemIterator::KEY_AS_PATHNAME |          // キーをパスにしている example: ) src/app.php ["src"] = app.php                \FilesystemIterator::CURRENT_AS_FILEINFO        // FileInfoとして取得する  
            ),  
            \RecursiveIteratorIterator::LEAVES_ONLY     // 葉ノード(最奥)だけを取得する  
        );  
  
  
        $classPaths = [];  
        foreach ($files as $file) {  
            // 万が一ディレクトリがあったら、ログに残しスキップさせる  
            if ($file->isDir()) {  
                AppLogger::error("Directory " . $file->getPathname() . "is Found");  
                continue;  
            }  
  
            // .phpを取り除いて、/を\\に置換、srcをAppに置換したものが、クラスパスになる  
            $filePath = str_replace('.php', '', $file->getPathname());  
            $filePart = str_replace('/', '\\', $filePath);  
            $classPaths[] = str_replace('src', 'App', $filePart);  
        }  
  
        return $classPaths;  
    }  
  
    /**  
     * 指定パス配下のクラスから、指定Attributeが付いたメソッドを全部探して取得し、実行  
     *  
     * Null指定の場合、Attribute付与されたメソッドすべて実行  
     * @param string $targetPath  
     * @param object|string|null $attrClass default: null  
     * @return void  
     */    public static function findMethodsWithAttributeInPath(string $targetPath, object|string $attrClass = null): void  
    {  
        // フォルダ指定をして、クラスパスを取得する  
        $classPaths = self::scan($targetPath);  
  
        // リフレクションで、指定Attributeが付与されているものだけを実行する  
        foreach ($classPaths as $classPath) {  
            try {  
                // インスタンス化  
                $class = (new \ReflectionClass($classPath))->newInstance();  
                $reflection = new \ReflectionObject($class);  
  
                // メソッドごとに、Attributeが付与されているものだけを実行  
                foreach ($reflection->getMethods() as $method) {  
                    $attrs = $method->getAttributes($attrClass);  
  
                    if (count($attrs) > 0) {  
                        $methodName = $method->getName();  
                        $class->$methodName();  
                    }  
                }  
            } catch (\ReflectionException $e) {  
                AppLogger::error($e->getMessage());  
            }  
        }  
  
    }  
}
```

### TestController.php
```php
<?php  
  
namespace App\controllers;  
  
use Framework\route\Route;  
use Framework\route\RouteSetup;  
  
class TestController extends RouteSetup  
{  
    #[Route("GET", "/tests/new/:id")]  
    public function create(): void  
    {  
        echo "CREATE";  
    }  
  
    #[Route("GET", "/tests/:id")]  
    public function read(): void  
    {  
        echo "Read";  
    }  
  
    #[Route("GET", "/tests")]  
    public function allRead(): void  
    {  
        echo "All Read";  
    }  
  
    #[Route("GET", "/tests/update/:id")]  
    public function update(): void  
    {  
        echo "Update";  
    }  
  
    #[Route("POST", "/tests/del/:id")]  
    public function delete(): void  
    {  
        echo "Delete";  
    }  
}
```