> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP Reflection API

> 說明 PHP Reflection API 的機制，以及在 Laravel 的 IoC 容器與套件開發中的活用方式。

## 什麼是 PHP Reflection API

PHP Reflection API 是 PHP 內建功能，可於執行時取得並檢查類別、方法、屬性、函式、參數等 metadata。可調查類別的建構函式接收哪些引數、方法上加註了哪些 attribute，且無需直接改寫原始碼。

Laravel 於 `Illuminate/Container/Container.php` 內部大量使用 Reflection API，實現 DI 容器的自動解析、PHP Attributes 的讀取、方法注入等。

## 主要類別

| 類別                    | 主要用途                    |
| --------------------- | ----------------------- |
| `ReflectionClass`     | 取得類別 metadata 的起點       |
| `ReflectionMethod`    | 取得方法的引數、存取修飾詞、attribute |
| `ReflectionProperty`  | 取得屬性的型別、預設值、attribute   |
| `ReflectionParameter` | 取得方法、函式的引數資訊（型別提示、預設值）  |
| `ReflectionFunction`  | 取得函式、closure 的 metadata |
| `ReflectionAttribute` | 取得 attribute 的類別名稱與引數   |

### ReflectionClass — 取得類別資訊

```php theme={null}
$ref = new ReflectionClass(UserController::class);

$ref->getName();          // 類別的完整名稱
$ref->getShortName();     // 僅類別名稱
$ref->isInstantiable();   // 是否可實例化
$ref->getConstructor();   // 以 ReflectionMethod 回傳建構函式
$ref->getMethods();       // 以 ReflectionMethod[] 回傳所有方法
$ref->getProperties();    // 以 ReflectionProperty[] 回傳所有屬性
$ref->getAttributes();    // 以 ReflectionAttribute[] 回傳類別上的 attribute
```

### ReflectionParameter — 檢查建構函式引數

```php theme={null}
$ref = new ReflectionClass(UserController::class);
$constructor = $ref->getConstructor();

if ($constructor) {
    foreach ($constructor->getParameters() as $param) {
        $param->getName();           // 引數名稱
        $param->getType();           // 型別提示（ReflectionType）
        $param->isOptional();        // 是否可省略
        $param->isVariadic();        // 是否為可變長引數
        $param->getDefaultValue();   // 預設值（如存在）
    }
}
```

## Laravel Container 與 Reflection API

Laravel 的 IoC 容器使用 Reflection API 實現建構函式注入（自動相依性解析）。讓我們理解 `app()->make(SomeClass::class)` 或依賴注入運作的機制。

```mermaid theme={null}
sequenceDiagram
    participant App as 應用程式碼
    participant Container as IoC 容器
    participant Reflection as ReflectionClass
    participant Dep as 相依類別

    App->>Container: app()->make(UserController::class)
    Container->>Reflection: new ReflectionClass(UserController::class)
    Reflection-->>Container: 建構函式資訊
    Container->>Reflection: getConstructor()->getParameters()
    Reflection-->>Container: [UserRepository, Cache, ...]
    loop 解析各引數
        Container->>Container: 依型別提示遞迴 make()
        Container->>Dep: new Dep(...)
        Dep-->>Container: 實例
    end
    Container-->>App: new UserController(repository, cache, ...)
```

### Container 的 `build()` 方法（簡略版）

實際 `Container.php` 的 `build()` 方法大致如下。

```php theme={null}
// 將 Illuminate\Container\Container::build() 簡化
public function build($concrete)
{
    // 1. 以 ReflectionClass 檢查類別
    $reflector = new ReflectionClass($concrete);

    // 無法實例化的類別（interface、abstract 等）拋出錯誤
    if (! $reflector->isInstantiable()) {
        throw new BindingResolutionException("[$concrete] is not instantiable.");
    }

    // 2. 取得建構函式
    $constructor = $reflector->getConstructor();

    // 無建構函式 → 直接實例化
    if (is_null($constructor)) {
        return new $concrete;
    }

    // 3. 取得建構函式的所有參數
    $dependencies = $constructor->getParameters();

    // 4. 遞迴解析各參數
    $instances = $this->resolveDependencies($dependencies);

    return new $concrete(...$instances);
}

protected function resolveDependencies(array $dependencies): array
{
    $results = [];

    foreach ($dependencies as $dependency) {
        // 若能取得型別提示則以容器遞迴解析
        $className = Util::getParameterClassName($dependency);

        $results[] = is_null($className)
            ? $this->resolvePrimitive($dependency)  // 原始型別
            : $this->resolveClass($dependency, $className); // 類別型別
    }

    return $results;
}
```

<Info>
  `Util::getParameterClassName()` 是從 `$parameter->getType()` 結果取出型別名稱字串的工具。將 `ReflectionParameter::getType()` 回傳的 `ReflectionNamedType` 包裝為易於處理的形式。
</Info>

## PHP Attributes 的讀取

PHP 8.0 以後，可以使用 Reflection API 取得類別、方法、屬性上的 attribute。Laravel 便是利用此機制處理 Queue attribute 或 Eloquent attribute。

<Tip>
  關於 PHP Attributes 及其於 Laravel 的整合，也請一併參考 [PHP Attributes](/zh-TW/advanced/php-attributes)。
</Tip>

### 讀取 attribute 的基本模式

```php theme={null}
use ReflectionClass;

// 1. 取得類別上的 attribute
$ref = new ReflectionClass(ProcessOrder::class);
$attrs = $ref->getAttributes(Queue::class); // 僅取特定 attribute

foreach ($attrs as $attr) {
    $instance = $attr->newInstance(); // 將 attribute 類別實例化
    echo $instance->queue;            // 讀取 attribute 的屬性
}

// 2. 取得所有 attribute（無過濾）
$allAttrs = $ref->getAttributes();

foreach ($allAttrs as $attr) {
    echo $attr->getName();       // attribute 類別名稱（FQCN）
    print_r($attr->getArguments()); // 建構函式引數
}
```

### Laravel 讀取 Queue attribute 的機制（簡略版）

```php theme={null}
// 將 InteractsWithQueue trait 的 ReadsQueueAttributes 簡化
protected function setJobInstanceForQueue(object $job): void
{
    $reflection = new ReflectionClass($job);

    foreach ($reflection->getAttributes(Queue::class) as $attribute) {
        $instance = $attribute->newInstance();
        $job->queue = $instance->queue instanceof \UnitEnum
            ? $instance->queue->value
            : $instance->queue;
    }
}
```

### 讀取方法上的 attribute

```php theme={null}
$ref = new ReflectionClass(UserController::class);

foreach ($ref->getMethods() as $method) {
    $attrs = $method->getAttributes(Route::class);

    foreach ($attrs as $attr) {
        $route = $attr->newInstance();
        echo "{$method->getName()} => {$route->path}";
    }
}
```

## 套件開發的活用範例

### 檢查類別實作的介面

於套件中可動態確認類別是否實作特定介面。

```php theme={null}
use ReflectionClass;

function isQueueable(string $class): bool
{
    $ref = new ReflectionClass($class);

    return $ref->implementsInterface(\Illuminate\Contracts\Queue\ShouldQueue::class);
}
```

### Metadata 取得 — 以 attribute 自動註冊路由

結合 attribute 與 Reflection 自動蒐集路由的模式。

```php theme={null}
// 自訂 Route attribute 定義
#[\Attribute(\Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(
        public string $method,
        public string $path,
    ) {}
}

// 於 Controller 使用
class UserController
{
    #[Route('GET', '/users')]
    public function index() { /* ... */ }

    #[Route('POST', '/users')]
    public function store() { /* ... */ }
}

// 從 attribute 自動註冊路由的服務提供者
class AttributeRouteServiceProvider extends ServiceProvider
{
    public function boot(Router $router): void
    {
        $ref = new ReflectionClass(UserController::class);

        foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            foreach ($method->getAttributes(Route::class) as $attr) {
                $route = $attr->newInstance();
                $router->addRoute(
                    $route->method,
                    $route->path,
                    [UserController::class, $method->getName()],
                );
            }
        }
    }
}
```

### 動態方法呼叫 — 方法注入

Laravel 的 `call()` 方法使用 Reflection 自動解析引數。以下為在套件中實作相同機制的範例。

```php theme={null}
use ReflectionFunction;
use ReflectionMethod;

function callWithDependencies(callable $callable, Container $container): mixed
{
    if (is_array($callable)) {
        [$object, $method] = $callable;
        $ref = new ReflectionMethod($object, $method);
        $params = $ref->getParameters();
    } else {
        $ref = new ReflectionFunction($callable);
        $params = $ref->getParameters();
    }

    $args = [];
    foreach ($params as $param) {
        $type = $param->getType()?->getName();
        $args[] = $type ? $container->make($type) : null;
    }

    return $callable(...$args);
}

// 使用範例
callWithDependencies([new UserController(), 'index'], app());
```

### 蒐集屬性的預設值

以 Reflection 取得設定類別預設值的模式。

```php theme={null}
use ReflectionClass;
use ReflectionProperty;

function getDefaults(string $class): array
{
    $ref = new ReflectionClass($class);
    $defaults = [];

    foreach ($ref->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
        if ($prop->hasDefaultValue()) {
            $defaults[$prop->getName()] = $prop->getDefaultValue();
        }
    }

    return $defaults;
}

class DatabaseConfig
{
    public string $driver = 'mysql';
    public int $port = 3306;
    public bool $strict = true;
}

// ['driver' => 'mysql', 'port' => 3306, 'strict' => true]
$defaults = getDefaults(DatabaseConfig::class);
```

## 效能考量

Reflection API 每次都會 parse 類別資訊，因此有成本。於正式環境程式碼中快取結果為最佳實踐。

```php theme={null}
class ReflectionCache
{
    private static array $cache = [];

    public static function getClass(string $class): \ReflectionClass
    {
        return self::$cache[$class] ??= new \ReflectionClass($class);
    }
}

// 使用範例
$ref = ReflectionCache::getClass(UserController::class);
```

<Warning>
  PHP 的 OPcache 不會快取 Reflection 的結果。若要在迴圈中檢查大量類別，請考慮自訂快取。不過 Laravel 容器本身於同一請求內也會重複使用 `ReflectionClass` 實例。
</Warning>

## 下一步

<Columns cols={2}>
  <Card title="PHP Attributes" icon="tag" href="/zh-TW/advanced/php-attributes">
    學習透過 ReflectionClass::getAttributes() 讀取的 PHP Attributes 詳細內容。
  </Card>

  <Card title="套件開發" icon="package" href="/zh-TW/advanced/package-development">
    學習活用 Reflection API 的 Laravel 套件開發方式。
  </Card>
</Columns>


## Related topics

- [PHP Attributes](/zh-TW/advanced/php-attributes.md)
- [API 參考 - VOICEVOX Core for PHP](/zh-TW/packages/voicevox-core-php/api.md)
- [Laravel 11 以後的新應用程式結構 FAQ](/zh-TW/advanced/app-structure-faq.md)
- [Service Container](/zh-TW/service-container.md)
- [Google Sheets API for Laravel](/zh-TW/packages/laravel-google-sheets/index.md)
