Symfony 應用程式中,套件讓你安裝及使用已經開發的功能,而不需要自己重寫它們。
為了使用它提供的功能,套件會在應用程式中註冊服務、組態、路由等…
例如,如果我們想要實作 JWT 認證,可以安裝 LexikJWTAuthenticationBundle,它使用 lcobucci/jwt 函式庫來生成、分析及驗證 JWT。
函式庫 & 套件
關於上面的例子,不會直接安裝 lcobucci/jwt 函式庫。當然可以,但這個函式庫不是為 Symfony 設計的。文件 中第一個句子提到:
lcobucci/jwtis 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 依賴於 lcobucci/jwt 函式庫,我們也可以在 Laravel 生態系統中發現,tymon/jwt-auth 的套件也使用 一樣的函式庫 !
FrameworkBundle
應用程式中最重要的套件是 symfony/framework-bundle : 它會收集必要的元件並開啟應用程式所需的一切:服務容器、其他套件和元件的組態、事件系統、控制台命令等…
啟用套件
Symfony 應用程式中的每個套件都是依環境啟用的,在 config/bundles.php 檔案中。當使用 composer require ... 安裝套件時,套件會被 Symfony Flex 自動添加到檔案中。
範例 : McpBundle
來探索 McpBundle 的程式碼,它讓我們可以建立 MCP 伺服器。
McpBundle 依賴於 MCP SDK。SDK 的描述中可以看到:
[…] It provides a framework-agnostic API for implementing MCP servers and clients in PHP.
McpBundle 的目的不是實作協定所有的機制(它已經在 SDK 中),而是讓 SDK 的功能在 Symfony 應用程式中可用的。
入口點
就像其他套件,McpBundle 遵循 PSR-4 的建議來載入類別。
套件的入口點會在繼承 AbstractBundle 抽象類別的類別:
<?php
// src/McpBundle.php
//...
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
//...
final class McpBundle extends AbstractBundle
{
//...
}
這個類別會在 config/bundles.php 檔案中註冊及啟用。
關於命名,套件遵循 Symfony 的最佳實踐,例如,在名稱後加上 Bundle,不要超過 2 個單詞等…
今天在這個類別中會分析 3 個函式:configure、loadExtension 和 build。
組態
configure 函式讓我們定義套件的組態格式(屬性、預設值等…)。
McpBundle 中載入 config/options.php 檔案,裡面有我們會在 Symfony 應用程式中使用的整個組態結構。
當我們在應用程式中安裝套件時,就可以改變各種參數來控制套件的邏輯執行。
組態的目的就是這樣:改變參數的值,而不需要改變程式碼。
loadExtension
一旦套件的組態載入後,loadExtension 就會宣告被整合到容器中的服務和參數。
config/services.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 伺服器中宣告的工具、資源、資源模板和提示。
PHP8 屬性
loadExtension 函式中,可以看到一個 registerMcpAttributes 函式,裡面有以下的程式碼:
<?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 屬性。以上會把每個屬性添加一個標籤,所以可以 自動組態 使用用這些標籤的類別,而正確地分類它們到服務容器中。
build
一旦服務被定義和標籤被添加後,一個編譯傳遞就會被執行。build 函式會註冊它:
//...
use Symfony\AI\McpBundle\DependencyInjection\McpPass;
//...
final class McpBundle extends AbstractBundle
{
//...
public function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new McpPass());
}
//...
}
之前添加的標籤在執行 process 函式時非常重要:
- 收集所有有
mcp.tool、mcp.prompt、mcp.resource和mcp.resource_template標籤的服務 - 建立一個 service locator 來包含所有這些服務
- 把這個 locator 注入到 MCP SDK 的
Server Builder,作為一個 PSR-11 服務容器
路由
除了服務、自動組態或註冊的參數,McpBundle 定義了一個控制器來處理 MCP 伺服器的要求:
src/Controller/McpController.php中,看到把要求轉移到 MCP 伺服器的控制器src/Routing/RouteLoader.php中,看到註冊這個控制器到 Symfony 路由器的邏輯
路由類型是 mcp,所以在一個 Symfony 專案中,要啟用這個 loader 就會在 config/routes.yaml 中寫:
mcp:
resource: .
type: mcp
Cursor
路由是可透過 /_mcp URL 要求的,所以當伺服器啟動後,如果用 HTTP 模式(也有 STDIO 的支援),在 Cursor 中可以添加以下的組態:
"Symfony MCP": {
"type": "http",
"url": "http://localhost:8000/_mcp"
}
然後,會看到所有在 Symfony 應用程式中撰寫的工具、提示、資源和資源模板。
總結
有很多 套件 來實作各式各樣的功能,例如:
每個都為應用程式添加一個功能,透過宣告路由、服務、實體、參數等。