0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Attributeのメモ

Posted at

注意事項

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

Attributeとは

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

プロパティ

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

サンプル

引用: https://www.php.net/manual/ja/language.attributes.overview.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  
  
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  
  
namespace Framework\route;  
  
use Attribute;  
  
#[Attribute]  
class Route  
{  
  
    public function __construct(public string $method, public string $path)  
    {}  
  
}

AppAttributeScanner.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  
  
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";  
    }  
}
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?