Для розробників
JsonRpcBundle — не закритий продукт «все в одному», а ядро, на якому можна будувати власні рішення. Він закриває основу будь-якого RPC API: прийом запитів, карту методів, валідацію, конвертацію даних, безпеку, кеш і документацію. Усе, що виходить за ці межі, — нові транспорти, протоколи, інтеграції, інструменти — задумано як розширення.
Цей розділ для тих, хто хоче зрозуміти, як бандл працює всередині, і розширювати його: у власному проєкті чи окремим пакетом для спільноти.
Принципи, на яких побудовано бандл
Код — єдине джерело правди. Методи API — це звичайні PHP-методи. Назви, типи, PHPDoc, атрибути й обмеження валідації одночасно визначають поведінку і документацію, тож вони не розходяться.
Мінімум налаштувань. Достатньо реалізувати IRpcService — решту бандл виводить сам: карту методів, схеми параметрів і результатів, конвертацію типів. Конфігурація потрібна лише там, де є реальний вибір.
Незалежність від транспорту. Процедура не знає, звідки прийшов запит: HTTP, консоль, черга повідомлень, REST чи MCP через розширення. Валідація, безпека, кеш і події однакові для всіх.
Відкритість до розширення. Ключові етапи обробки — події, а основні механізми — інтерфейси з автоконфігурацією: конвертери параметрів, генератори схем, джерела методів, стратегії batch, валідатори токенів, завантажувачі маршрутів. Нову поведінку додають, не змінюючи ядра.
Рідні механізми Symfony. DI-контейнер, EventDispatcher, Messenger, Lock, Validator, Cache, Translation — бандл не винаходить власних аналогів, тож розширення пишуться звичними для Symfony способами.
Відповідність стандарту. Протокол — JSON-RPC 2.0, документація — OpenRPC. Клієнти й інструменти, що підтримують стандарти, працюють з API без адаптації.
Продуктивність без жертв. Карта методів і метадані будуються під час компіляції контейнера, документація кешується, а дорога робота переноситься на після відправлення відповіді.
Що є в розділі
- Життєвий цикл — як запит проходить через бандл для кожної точки входу і в який момент генерується кожна подія.
- Розширення — перелік наявних розширень.
- Події життєвого циклу — детально про кожну подію і приклади лістенерів.
- Правила для ШІ-асистентів — файли інструкцій для Claude Code, Codex, Cursor, Copilot, що підключаються до ваших наявних правил.
- Відомі помилки — що працює не так, як очікується, і як це обійти.
Загальні положення для розширень
- Не дублюйте ядро. Розширення не повинне мати власної карти методів, валідації чи формату помилок. Методи беруться з
ServiceMap/IServiceHolder, виконання — черезRpcExecutor, помилки — черезrpc.error. Тоді#[RPC\Assertions],#[RPC\Cache],#[RPC\Lock], конвертери й події працюють для вашого транспорту автоматично. - Поважайте групи й версії API. Визначайте групу з маршруту чи конфігурації й записуйте її в
ApiGroupContext; передавайте версію вRpcRequest. - Перевіряйте токен самі.
RpcExecutorтокен не перевіряє. ВикористовуйтеIRpcSecurityз відповідним токен-холдером — так діятимуть ті саміsecurity.providers, що й для основного API. - Реєструйте маршрути через імпорт ядра. Тоді кожен імпорт для окремої групи отримує унікальні назви маршрутів.
- Залежність від ядра —
ufo-tech/json-rpc-bundle: ^10.4.2або новіше, якщо використовуєтеRpcExecutorтаIApiRouteImport. - Компоненти, що створюються під час компіляції (
IServiceFiller,IConstraintGenerator,IParamConvertor), бандл інстанціює сам під час збирання контейнера. Їхні конструктори можуть приймати лише сервіси, доступні на етапі компіляції, або теговані колекції — не скалярні параметри.
Точки розширення
| Що | Як | Для чого |
|---|---|---|
Routing\IApiRouteImport |
автоконфігурація, тег ufo.rpc.route_import |
Підключити router.yaml розширення окремо для кожної групи API: getResource(), getRouteNamePrefixAttribute() |
Routing\RpcYamlFileLoader / ApiYamlFileLoader |
успадкування, тег routing.loader |
Старіший спосіб власного завантажувача маршрутів |
Server\RpcExecutor |
автовайринг | execute(IRpcRequestCarrier): RpcResponse — виконати запит повним циклом ядра |
Server\RequestPrepare\IRpcRequestCarrier / IRpcHttpCarrier |
реалізація | Обгортка вашого вхідного запиту. IRpcHttpCarrier::getHttpRequest() прив'язує rpc.post_response до HTTP-запиту й дозволяє від'єднане виконання notifications |
Server\ServiceMap\IServiceHolder |
тег rpc.service_holder (вручну) |
Власне джерело методів або фільтр поверх ServiceMap |
Security\Interfaces\IRpcSecurity, IRpcTokenHolder |
аліас / реалізація | Перевірка токена з вашого джерела (заголовок, параметр, повідомлення) |
Security\Interfaces\ITokenValidator |
аліас або security.providers |
Власна перевірка токенів |
ParamConvertors\IParamConvertor |
автоконфігурація, тег rpc.param_convertor |
Конвертація власних типів параметрів і результатів |
...\Reflections\Fillers\AbstractServiceFiller |
автоконфігурація, тег ufo-rpc.service-iterator |
Читання власних атрибутів методів у метадані Service |
...\Generate\Interfaces\IConstraintGenerator |
тег rpc.constraint |
JSON Schema для власних обмежень валідації |
Server\Batch\IBatchStrategy |
автоконфігурація, тег rpc.batch_strategy |
Власна стратегія виконання batch |
DocAdapters\Outputs\AbstractPostmanAdapter |
успадкування | Колекція Postman для вашого транспорту |
Server\RpcLocaleNegotiator |
автовайринг | Вибір мови з Accept-Language для власної документації |
translations.paths |
prependExtensionConfig() |
Переклади назв і описів методів, які постачає розширення |
| Події | #[AsEventListener] |
Поведінка навколо виконання методів |
Мінімальний адаптер
// маршрути розширення для кожної групи API
final class AcmeRouteImport implements IApiRouteImport
{
public function getResource(): string { return __DIR__.'/../../config/router.yaml'; }
public function getRouteNamePrefixAttribute(): string { return '_acme_name_prefix'; }
}
// контролер розширення
final class AcmeController
{
public function __construct(
private RpcExecutor $executor,
private IRpcSecurity $security,
) {}
public function __invoke(Request $request): JsonResponse
{
$this->security->setTokenHolder(new HttpTokenHolder(/* ... */));
$this->security->isValidApiRequest();
$response = $this->executor->execute(new RpcFromAcme($request));
return new JsonResponse(/* серіалізований $response */);
}
}
// переклади, що постачає розширення
public function prepend(ContainerBuilder $container): void
{
$container->prependExtensionConfig('ufo_json_rpc', [
'translations' => ['paths' => [__DIR__.'/../../translations']],
]);
}
Живі приклади — у коді mcp-bundle і rest-bundle (див. Розширення).
Розвиток екосистеми
Ми всіляко заохочуємо розвиток екосистеми навколо бандла. Кожен новий транспорт, адаптер чи інструмент робить JSON-RPC на Symfony зручнішим для всіх.
Пишіть розширення. Якщо вам бракує інтеграції — з брокером повідомлень, протоколом, системою моніторингу чи генератором клієнтів, — оформіть її окремим пакетом за загальними положеннями вище. Розкажіть про нього — і ми додамо його до переліку розширень.
Повідомляйте про проблеми. Знайшли помилку чи нестиковку з документацією — створіть issue в репозиторії бандла. Підтверджені проблеми ми описуємо в розділі Відомі помилки.
Пропонуйте зміни в ядро. Якщо розширенню не вистачає точки розширення, це сигнал, що її варто додати в ядро. Порядок співпраці описано в CONTRIBUTING.md:
- Зробіть fork і окрему гілку.
- Перед роботою перевірте issue tracker; якщо обговорення немає — створіть issue з пропозицією.
- Дотримуйтесь стилю коду проєкту.
- Додайте тести для нових можливостей; усі наявні тести мають проходити.
- Опишіть у pull request, яку проблему вирішено або що додано, і будьте готові до обговорення.
Беручи участь у проєкті, ви погоджуєтесь дотримуватись правил поведінки.
Про вразливості безпеки повідомляйте не через публічні issue, а за контактом із SECURITY.md репозиторію.