---
url: /docs/5.0/plugin/mechanism.md
---
# 插件核心机制

> 本章深入介绍 CatchAdmin 插件系统的核心组件和工作机制。

## 系统架构概览

CatchAdmin 插件系统由以下核心组件构成：

```
┌─────────────────────────────────────────────────────────────────┐
│                         Composer                                 │
│                    (包管理器核心)                                 │
├──────────────────────────┬──────────────────────────────────────┤
│   PluginInstallerPlugin  │          PluginHook                  │
│   (安装路径管理)          │          (生命周期钩子)               │
├──────────────────────────┴──────────────────────────────────────┤
│                    InstalledPluginManager                        │
│                    (插件记录管理)                                 │
├─────────────────────────────────────────────────────────────────┤
│                      Plugin                                      │
│                    (插件信息读取)                                 │
├─────────────────────────────────────────────────────────────────┤
│                    PluginServiceProvider                         │
│                    (路由加载 & 命令注册)                          │
└─────────────────────────────────────────────────────────────────┘
```

### 组件职责

| 组件 | 位置 | 职责 |
|------|------|------|
| `PluginInstallerPlugin` | `packages/plugin-installer/` | 注册自定义安装器 |
| `PluginInstaller` | `packages/plugin-installer/` | 确定插件安装路径 |
| `PluginHook` | `packages/plugin-hook/` | 监听 Composer 事件，执行钩子 |
| `InstalledPluginManager` | `packages/plugin/` | 管理 plugins.json 文件 |
| `Plugin` | `packages/plugin/` | 提供插件信息查询接口 |
| `PluginServiceProvider` | `packages/plugin/` | 加载路由，注册命令 |

## PluginInstaller

### 作用

`PluginInstaller` 是一个 Composer 自定义安装器，负责将 `catchadmin-plugin` 类型的包安装到指定目录。

### 核心逻辑

**PluginInstallerPlugin.php** - 注册安装器：

```php
class PluginInstallerPlugin implements PluginInterface
{
    public function activate(Composer $composer, IOInterface $io): void
    {
        $installer = new PluginInstaller($io, $composer);
        $composer->getInstallationManager()->addInstaller($installer);
    }
}
```

**PluginInstaller.php** - 确定安装路径：

```php
class PluginInstaller extends LibraryInstaller
{
    public function supports(string $packageType): bool
    {
        return isset($this->typePathMap[$packageType]);
    }

    public function getInstallPath(PackageInterface $package): string
    {
        $basePath = $this->typePathMap[$package->getType()];
        $name = $package->getPrettyName(); // vendor/package
        
        return $basePath . '/' . $name;
    }
}
```

## PluginHook - 生命周期钩子

### 作用

`PluginHook` 监听 Composer 事件，在适当时机执行插件定义的钩子方法，并更新插件记录。

### 订阅的 Composer 事件

```php
public static function getSubscribedEvents(): array
{
    return [
        PackageEvents::PRE_PACKAGE_INSTALL => 'onPrePackageInstall',
        PackageEvents::POST_PACKAGE_INSTALL => 'onPostPackageInstall',
        PackageEvents::PRE_PACKAGE_UNINSTALL => 'onPrePackageUninstall',
        PackageEvents::POST_PACKAGE_UNINSTALL => 'onPostPackageUninstall',
        PackageEvents::PRE_PACKAGE_UPDATE => 'onPrePackageUpdate',
        PackageEvents::POST_PACKAGE_UPDATE => 'onPostPackageUpdate',
        ScriptEvents::POST_AUTOLOAD_DUMP => 'onPostAutoloadDump',
    ];
}
```

### 事件与钩子对应关系

| Composer 事件 | 触发时机 | 执行的钩子 |
|---------------|----------|------------|
| `PRE_PACKAGE_INSTALL` | 安装包之前 | `beforeInstall` |
| `POST_PACKAGE_INSTALL` | 安装包之后 | （记录待处理） |
| `POST_AUTOLOAD_DUMP` | autoload 生成后 | `afterInstall` |
| `PRE_PACKAGE_UNINSTALL` | 卸载包之前 | `beforeUninstall` |
| `POST_PACKAGE_UNINSTALL` | 卸载包之后 | （记录待处理） |
| `POST_AUTOLOAD_DUMP` | autoload 生成后 | `afterUninstall` |
| `PRE_PACKAGE_UPDATE` | 更新包之前 | `beforeUpdate` |
| `POST_PACKAGE_UPDATE` | 更新包之后 | （记录待处理） |
| `POST_AUTOLOAD_DUMP` | autoload 生成后 | `afterUpdate` |

### 钩子方法签名

钩子方法是**实例方法**（非静态方法）：

```php
// hook.php
class Hook
{
    // 安装钩子
    public function beforeInstall(array $context): void;
    public function afterInstall(array $context): void;

    // 更新钩子
    public function beforeUpdate(array $context): void;
    public function afterUpdate(array $context): void;

    // 卸载钩子
    public function beforeUninstall(array $context): void;
    public function afterUninstall(array $context): void;
}
```

### $context 参数内容

```php
[
    'name'      => 'vendor/package',     // 包名
    'version'   => '1.0.0',              // 版本号
    'path'      => 'packages/vendor/package', // 安装路径
    'namespace' => 'Vendor\Package',    // 根命名空间
]
```

### 钩子类加载机制

插件钩子采用**约定方式**加载：

* **文件名**：`hook.php`（小写）
* **类名**：`Hook`（首字母大写）
* **位置**：插件根目录

```php
// packages/vendor/package/hook.php
<?php

class Hook
{
    public function afterInstall(array $context): void
    {
        // 安装后的逻辑
    }

    public function afterUninstall(array $context): void
    {
        // 卸载后的逻辑
    }
}
```

`PluginHook` 会在执行钩子时自动加载：

```php
protected function invokeHookMethod(string $packagePath, string $method, array $context): void
{
    $hookFile = $packagePath . '/hook.php';

    if (! file_exists($hookFile)) {
        return;
    }

    if (! class_exists('Hook', false)) {
        require_once $hookFile;
    }

    if (class_exists('Hook', false)) {
        $instance = new \Hook();
        if (method_exists($instance, $method)) {
            $instance->$method($context);
        }
    }
}
```

:::tip 📌 约定说明

* 钩子类**不需要命名空间**，因为它不通过 PSR-4 加载
* 如需使用插件的其他类，在 `after*` 钩子中调用（此时 autoload 已生成）
  :::

## InstalledPluginManager - 插件记录管理

### 作用

`InstalledPluginManager` 负责管理 `storage/packages/plugins.json` 文件，提供已安装插件信息的增删改查接口。

### 核心方法

```php
class InstalledPluginManager
{
    protected string $storagePath;

    public function __construct(?string $storagePath = null)
    {
        if ($storagePath) {
            $this->storagePath = $storagePath;
        } elseif (function_exists('config')) {
            $this->storagePath = config('plugin.installed_file');
        } else {
            // Composer 环境下使用默认路径
            $this->storagePath = getcwd() . '/storage/packages/plugins.json';
        }
    }

    // 获取所有已安装插件
    public function getAll(): array;

    // 获取单个插件信息
    public function get(string $name): ?array;

    // 添加插件记录
    public function add(array $data): bool;

    // 更新插件记录
    public function update(string $name, array $data): bool;

    // 删除插件记录
    public function remove(string $name): bool;
}
```

### 环境兼容性

`InstalledPluginManager` 同时支持 Laravel 环境和 Composer 环境：

```php
// Laravel 环境：使用 config() 函数
if (function_exists('config')) {
    $this->storagePath = config('plugin.installed_file');
}

// Composer 环境：使用 getcwd()
else {
    $this->storagePath = getcwd() . '/storage/packages/plugins.json';
}
```

## PluginServiceProvider - 路由与命令加载

### 作用

`PluginServiceProvider` 是 Laravel 服务提供者，负责：

* 注册插件配置
* 加载插件路由
* 注册 Artisan 命令

### 路由加载逻辑

```php
public function boot(): void
{
    // 如果路由已缓存，跳过动态加载
    if ($this->app->routesAreCached()) {
        return;
    }

    // 加载所有插件路由
    foreach (Plugin::allRoutes() as $routeFile) {
        if (file_exists($routeFile)) {
            require $routeFile;
        }
    }
}
```

### 路由发现机制

`Plugin::allRoutes()` 方法从已安装插件中收集所有路由文件：

```php
public static function allRoutes(): array
{
    $routes = [];
    
    foreach (self::all() as $plugin) {
        $routesDir = base_path($plugin['path'] . '/routes');
        
        if (is_dir($routesDir)) {
            $files = glob($routesDir . '/*.php');
            $routes = array_merge($routes, $files);
        }
    }
    
    return $routes;
}
```

### 注册的 Artisan 命令

```php
protected $commands = [
    PluginOptimizeCommand::class,  // plugin:optimize
    PluginClearCommand::class,     // plugin:clear
    PluginPackCommand::class,      // catch:plugin-pack
];
```

## 安装/卸载完整流程图

### 安装流程

```
composer require vendor/plugin
        │
        ▼
┌───────────────────────────────────┐
│ 1. Composer 解析依赖               │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 2. PluginInstaller.supports()     │
│    检查是否为 catchadmin-plugin   │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 3. PluginInstaller.getInstallPath()│
│    确定安装路径                    │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 4. PRE_PACKAGE_INSTALL 事件        │
│    → PluginHook.onPrePackageInstall│
│    → 执行 beforeInstall 钩子       │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 5. Composer 下载/链接包            │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 6. POST_PACKAGE_INSTALL 事件       │
│    → 记录待处理的安装信息          │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 7. Composer 生成 autoload          │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 8. POST_AUTOLOAD_DUMP 事件         │
│    → InstalledPluginManager.add() │
│    → 写入 plugins.json            │
│    → 执行 afterInstall 钩子       │
└───────────────────────────────────┘
        │
        ▼
     安装完成
```

### 卸载流程

```
composer remove vendor/plugin
        │
        ▼
┌───────────────────────────────────┐
│ 1. Composer 解析依赖变更           │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 2. PRE_PACKAGE_UNINSTALL 事件      │
│    → 执行 beforeUninstall 钩子    │
│    → 记录待处理的卸载信息          │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 3. Composer 删除包                 │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 4. POST_PACKAGE_UNINSTALL 事件     │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 5. Composer 重新生成 autoload      │
└───────────────────────────────────┘
        │
        ▼
┌───────────────────────────────────┐
│ 6. POST_AUTOLOAD_DUMP 事件         │
│    → 执行 afterUninstall 钩子     │
│    → InstalledPluginManager.remove()│
│    → 从 plugins.json 删除记录     │
└───────────────────────────────────┘
        │
        ▼
     卸载完成
```
