---
title: "Symfony 套件 : 如何運作 ?"
date: 2026-02-02
description: Symfony 套件是什麼 ? 它有什麼用途與如何運作 ?
categories:
  - symfony
  - php
---

Symfony 應用程式中，套件讓你安裝及使用已經開發的功能，而不需要自己重寫它們。

為了使用它提供的功能，套件會在應用程式中註冊服務、組態、路由等...

例如，如果我們想要實作 JWT 認證，可以安裝 [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle)，它使用 [lcobucci/jwt](https://github.com/lcobucci/jwt) 函式庫來生成、分析及驗證 JWT。

## 函式庫 & 套件

關於上面的例子，不會直接安裝 [lcobucci/jwt](https://github.com/lcobucci/jwt) 函式庫。當然可以，但這個函式庫不是為 Symfony 設計的。[文件](https://lcobucci-jwt.readthedocs.io/en/latest/) 中第一個句子提到：

> `lcobucci/jwt` is a framework-agnostic PHP library that allows you to issue, parse, and validate JSON Web Tokens based on the RFC 7519.

"framework-agnostic" 的意思是這個函式庫不是特定於 **任何** 框架。實際上，它就可以被整合到 **任何** 框架中。

因此，就像為 Symfony 設計的 [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle) 依賴於 [lcobucci/jwt](https://github.com/lcobucci/jwt) 函式庫，我們也可以在 [Laravel](https://www.laravel.com) 生態系統中發現，[tymon/jwt-auth](https://github.com/tymondesigns/jwt-auth) 的套件也使用 [一樣的函式庫](https://github.com/tymondesigns/jwt-auth/blob/2.x/composer.json#L31) !

:::important

一個套件不一定會依賴函式庫。它本來是要提供什麼應用程式都可用的功能。如果有一個函式庫，那套件的角色就是要確保它在框架中的**整合**。

`LexikJWTAuthenticationBundle` 中，一旦安裝好了就可以：

- 執行一個命令來生成私鑰/公鑰
- 要求 `/api/login_check` 路由來生成一個 JWT
- 使用提供的 `JWTAuthenticator` 或寫一個繼承它的類別等。

:::

### FrameworkBundle

應用程式中最重要的套件是 [`symfony/framework-bundle`](https://github.com/symfony/framework-bundle) : 它會收集必要的元件並開啟應用程式所需的一切：服務容器、其他套件和元件的組態、事件系統、控制台命令等...

## 啟用套件

Symfony 應用程式中的每個套件都是**依環境**啟用的，在 `config/bundles.php` 檔案中。當使用 `composer require ...` 安裝套件時，套件會被 [Symfony Flex](https://symfony.com/doc/7.4/setup.html#symfony-flex) 自動添加到檔案中。

## 範例 : McpBundle

來探索 [McpBundle](https://github.com/symfony/mcp-bundle) 的程式碼，它讓我們可以建立 [MCP 伺服器](https://modelcontextprotocol.io/docs/learn/server-concepts)。

:::warning

這個套件撰寫這篇文章的時候仍然是 **實驗** 的。

:::

McpBundle 依賴於 [MCP SDK](https://github.com/modelcontextprotocol/php-sdk)。SDK 的描述中可以看到：

> [...] It provides a **framework-agnostic** API for implementing MCP servers and clients in PHP.

McpBundle 的目的不是實作協定所有的機制（它已經在 SDK 中），而是**讓 SDK 的功能在 Symfony 應用程式中可用**的。

:::tip

想要理解的關鍵是 **如何** 讓 MCP SDK 功能在 Symfony 應用程式中可用。

:::

### 入口點

就像其他套件，McpBundle 遵循 [PSR-4](/en/articles/psr#auto-loading) 的建議來載入類別。

套件的入口點會在繼承 `AbstractBundle` 抽象類別的類別：

```php
<?php
// src/McpBundle.php
//...

use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
//...

final class McpBundle extends AbstractBundle
{
  //...
}
```

這個類別會在 `config/bundles.php` 檔案中註冊及啟用。

關於命名，套件遵循 [Symfony 的最佳實踐](https://symfony.com/doc/7.4/bundles/best_practices.html#bundles-naming-conventions)，例如，在名稱後加上 `Bundle`，不要超過 2 個單詞等...

今天在這個類別中會分析 3 個函式：`configure`、`loadExtension` 和 `build`。

### 組態

`configure` 函式讓我們定義套件的組態格式（屬性、預設值等...）。

McpBundle 中載入 `config/options.php` 檔案，裡面有我們會在 Symfony 應用程式中使用的整個組態結構。

當我們在應用程式中安裝套件時，就可以改變各種參數來控制套件的邏輯執行。

組態的目的就是這樣：改變參數的值，而不需要改變程式碼。

### `loadExtension`

一旦套件的組態載入後，[`loadExtension`](https://symfony.com/doc/7.4/bundles/extension.html#loading-services-directly-in-your-bundle-class) 就會宣告被整合到容器中的**服務和參數**。

`config/services.php` 被載入並執行，它只回傳一個函式來定義服務。例如：

```php
<?php
// config/services.php
//...
use Mcp\Capability\Registry;
//...

return static function (ContainerConfigurator $container): void {
    $container->services()
        ->set('mcp.registry', Registry::class)
            ->args([service('event_dispatcher'), service('logger')])
            ->tag('monolog.logger', ['channel' => 'mcp'])

      // other services definitions
    ;
};
```

`Registry` 類別來自 MCP SDK：建立與 Symfony 框架的連接。服務容器，在生產環境中，會有一個 `mcp.registry` 服務可用 (`->set('mcp.registry', Registry::class)`)。這個服務會包含所有想要在 MCP 伺服器中宣告的工具、資源、資源模板和提示。

:::tip

開發環境中（通常是 `APP_ENV=dev` 或 `kernel.debug` 為 `true`），另一種類型的服務會被註冊：`TraceableRegistry`。

這個服務會提供 `Registry` 實例的資訊給 Symfony Profiler。Profiler 裡面有一個 MCP 的不分，可以查看 MCP 伺服器中的元素（工具、資源、提示等...）。

`TraceableRegistry` 服務包含一個 `Registry` 服務的實例：它是一個 [decorator](https://refactoring.guru/design-patterns/decorator)。

:::

### PHP8 屬性

`loadExtension` 函式中，可以看到一個 `registerMcpAttributes` 函式，裡面有以下的程式碼：

```php
<?php
//...

final class McpBundle extends AbstractBundle
{
    //...

    private function registerMcpAttributes(ContainerBuilder $builder): void
    {
        $mcpAttributes = [
            McpTool::class => 'mcp.tool',
            McpPrompt::class => 'mcp.prompt',
            McpResource::class => 'mcp.resource',
            McpResourceTemplate::class => 'mcp.resource_template',
        ];

        foreach ($mcpAttributes as $attributeClass => $tag) {
            $builder->registerAttributeForAutoconfiguration(
                $attributeClass,
                static function (ChildDefinition $definition, object $attribute, \Reflector $reflector) use ($tag): void {
                    $definition->addTag($tag);
                }
            );
        }
    }

    //...
}
```

每個 `McpTool`、`McpPrompt`、`McpResource` 和 `McpResourceTemplate` 類別都是**來自 MCP SDK**的 PHP8 屬性。以上會把每個屬性添加一個標籤，所以可以 [自動組態](/zh/articles/symfony-autoconfiguration) 使用用這些標籤的類別，而正確地分類它們到服務容器中。

:::tip

服務和參數的組態與定義是在服務容器編譯時執行的。

:::

### `build`

一旦服務被定義和標籤被添加後，一個**編譯傳遞**就會被執行。`build` 函式會註冊它：

```php
//...
use Symfony\AI\McpBundle\DependencyInjection\McpPass;
//...

final class McpBundle extends AbstractBundle
{
    //...

    public function build(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new McpPass());
    }

    //...
}
```

:::note

[編譯傳遞](https://symfony.com/doc/7.4/service_container/compiler_passes.html) 讓我們可以操作已經註冊到容器中的服務定義。

:::

之前添加的標籤在執行 [`process`](https://github.com/symfony/mcp-bundle/blob/main/src/DependencyInjection/McpPass.php) 函式時非常重要：

- 收集所有有 `mcp.tool`、`mcp.prompt`、`mcp.resource` 和 `mcp.resource_template` 標籤的服務
- 建立一個 [service locator](https://symfony.com/doc/7.4/service_container/service_subscribers_locators.html) 來包含所有這些服務
- 把這個 locator 注入到 MCP SDK 的 `Server Builder`，作為一個 PSR-11 服務容器

### 路由

除了服務、自動組態或註冊的參數，McpBundle 定義了一個**控制器**來處理 MCP 伺服器的要求：

- [`src/Controller/McpController.php`](https://github.com/symfony/mcp-bundle/blob/main/src/Controller/McpController.php) 中，看到把要求轉移到 MCP 伺服器的控制器
- [`src/Routing/RouteLoader.php`](https://github.com/symfony/mcp-bundle/blob/main/src/Routing/RouteLoader.php) 中，看到註冊這個控制器到 Symfony 路由器的邏輯

路由類型是 `mcp`，所以在一個 Symfony 專案中，要啟用這個 loader 就會在 `config/routes.yaml` 中寫：

```yaml
mcp:
  resource: .
  type: mcp
```

### Cursor

路由是可透過 `/_mcp` URL 要求的，所以當伺服器啟動後，如果用 HTTP 模式（也有 STDIO 的支援），在 Cursor 中可以添加以下的組態：

```json
"Symfony MCP": {
    "type": "http",
    "url": "http://localhost:8000/_mcp"
}
```

然後，會看到所有在 Symfony 應用程式中撰寫的工具、提示、資源和資源模板。

## 總結

有很多 [套件](https://symfony.com/bundles) 來實作各式各樣的功能，例如：

- [Doctrine ORM 整合](https://symfony.com/bundles/DoctrineBundle/current/index.html)
- [兩步驟驗證](https://symfony.com/bundles/SchebTwoFactorBundle/current/index.html)
- [管理介面](https://symfony.com/bundles/EasyAdminBundle/current/index.html)
- [與 `AssetMapper` 元件的 Tailwind 整合](https://symfony.com/bundles/TailwindBundle/current/index.html)

每個都為應用程式添加一個功能，透過宣告路由、服務、實體、參數等。
