注意事項
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";
}
}