PHPackages                             otezvikentiy/json-rpc-api - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. otezvikentiy/json-rpc-api

ActiveSymfony-bundle[API Development](/categories/api)

otezvikentiy/json-rpc-api
=========================

Symfony Json RPC API bundle

4.2(1mo ago)32541MITPHPPHP &gt;=8.2

Since Aug 11Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/OtezVikentiy/symfony-jsonrpc-api-bundle)[ Packagist](https://packagist.org/packages/otezvikentiy/json-rpc-api)[ RSS](/packages/otezvikentiy-json-rpc-api/feed)WikiDiscussions master Synced 3w ago

READMEChangelogDependencies (18)Versions (60)Used By (0)

OtezVikentiy Symfony JSON-RPC API Bundle
========================================

[](#otezvikentiy-symfony-json-rpc-api-bundle)

[English version](./README.en.md)

[![PHP Version](https://camo.githubusercontent.com/d840cef9807c8f76051ad687841d67f4d830c84e0d83236968e53124ef6742d5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d3838393242462e737667)](https://php.net/)[![Symfony Version](https://camo.githubusercontent.com/df69560fe6c25fd0268689a4bbba1950f74df1ee59a1b5d6f744f96d63043647/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73796d666f6e792d253345253344362e342d3030303030302e737667)](https://symfony.com/)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](https://opensource.org/licenses/MIT)[![Version](https://camo.githubusercontent.com/0ac90fcaef633474f3b01000ea744b1dc61b7cfa65bd31fc72d448e90b6cacb2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d342e322d626c75652e737667)](https://github.com/OtezVikentiy/symfony-jsonrpc-api-bundle)[![Coverage](https://camo.githubusercontent.com/0767fdf5075f81ace8252cdcce284d2c486fe635a39845beef3623c1782ed589/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d39312532352d627269676874677265656e2e737667)](https://github.com/OtezVikentiy/symfony-jsonrpc-api-bundle)

Symfony-бандл для быстрого и удобного создания JSON-RPC 2.0 API приложений.

GitHub:

---

Возможности
-----------

[](#возможности)

- Полная совместимость со спецификацией [JSON-RPC 2.0](https://www.jsonrpc.org/specification)
- Конфигурация методов через PHP 8 атрибуты (`#[JsonRPCAPI(...)]`)
- Поддержка HTTP-методов: POST, GET, PUT, PATCH, DELETE
- Версионирование API (`/api/v1`, `/api/v2`, ...)
- Автоматическая генерация OpenAPI/Swagger документации
- Pre- и Post-процессоры (middleware)
- Пакетные запросы (batch requests)
- Встроенная валидация запросов
- Ролевой контроль доступа через Symfony Security
- Поддержка бинарных ответов (изображения, документы)

---

Требования
----------

[](#требования)

- PHP &gt;= 8.2
- Symfony &gt;= 6.4

---

Установка
---------

[](#установка)

```
composer require otezvikentiy/json-rpc-api
```

Включите бандл (если не используется Symfony Flex):

```
// config/bundles.php
return [
    // ...
    OV\JsonRPCAPIBundle\OVJsonRPCAPIBundle::class => ['all' => true],
];
```

Создайте конфигурационные файлы:

```
# config/routes/ov_json_rpc_api.yaml
ov_json_rpc_api:
    resource: '@OVJsonRPCAPIBundle/config/routes/routes.yaml'
```

```
# config/packages/ov_json_rpc_api.yaml
ov_json_rpc_api:
    access_control_allow_origin_list:
        - '*'
    swagger:
        api_v1:
            api_version: '1'
            base_path: '%env(string:OV_JSON_RPC_API_BASE_URL)%'
            base_path_description: 'Production server'
            test_path: '%env(string:OV_JSON_RPC_API_TEST_URL)%'
            test_path_description: 'Sandbox server'
            auth_token_name: 'X-AUTH-TOKEN'
            auth_token_test_value: '%env(string:OV_JSON_RPC_API_AUTH_TOKEN)%'
            info:
                title: 'My API'
                description: 'JSON-RPC 2.0 API'
                terms_of_service_url: 'https://example.com/tos'
                contact:
                    name: 'Support'
                    url: 'https://example.com'
                    email: 'support@example.com'
                license: 'MIT'
                licenseUrl: 'https://opensource.org/licenses/MIT'
```

```
# .env
OV_JSON_RPC_API_SWAGGER_PATH=public/openapi/
OV_JSON_RPC_API_BASE_URL=http://localhost
OV_JSON_RPC_API_TEST_URL=http://localhost
OV_JSON_RPC_API_AUTH_TOKEN=your_test_token_here
```

Подробная инструкция: [docs/installation.md](./docs/installation.md)

---

Быстрый старт
-------------

[](#быстрый-старт)

### 1. Создайте Request

[](#1-создайте-request)

```
// src/RPC/V1/GetProduct/Request.php
namespace App\RPC\V1\GetProduct;

class Request
{
    private int $id;
    private string $title;

    public function __construct(int $id)
    {
        $this->id = $id;
    }

    public function getId(): int { return $this->id; }
    public function setId(int $id): void { $this->id = $id; }
    public function getTitle(): string { return $this->title; }
    public function setTitle(string $title): void { $this->title = $title; }
}
```

### 2. Создайте Response

[](#2-создайте-response)

```
// src/RPC/V1/GetProduct/Response.php
namespace App\RPC\V1\GetProduct;

class Response
{
    private bool $success;
    private string $title;
    private int $price;

    public function __construct(bool $success = true)
    {
        $this->success = $success;
    }

    public function isSuccess(): bool { return $this->success; }
    public function setSuccess(bool $success): void { $this->success = $success; }
    public function getTitle(): string { return $this->title; }
    public function setTitle(string $title): void { $this->title = $title; }
    public function getPrice(): int { return $this->price; }
    public function setPrice(int $price): void { $this->price = $price; }
}
```

### 3. Создайте метод API

[](#3-создайте-метод-api)

```
// src/RPC/V1/GetProductMethod.php
namespace App\RPC\V1;

use OV\JsonRPCAPIBundle\Core\Annotation\JsonRPCAPI;
use OV\JsonRPCAPIBundle\Core\ApiMethodInterface;
use App\RPC\V1\GetProduct\Request;
use App\RPC\V1\GetProduct\Response;

#[JsonRPCAPI(methodName: 'getProduct', type: 'POST')]
class GetProductMethod implements ApiMethodInterface
{
    public function call(Request $request): Response
    {
        $response = new Response();
        $response->setTitle('Iphone 15');
        $response->setPrice(2000);
        return $response;
    }
}
```

### 4. Вызовите API

[](#4-вызовите-api)

```
curl -X POST http://localhost/api/v1 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "method": "getProduct", "params": {"id": 1, "title": "test"}, "id": "1"}'
```

Ответ:

```
{
    "jsonrpc": "2.0",
    "result": {
        "success": true,
        "title": "Iphone 15",
        "price": 2000
    },
    "id": "1"
}
```

---

Архитектура
-----------

[](#архитектура)

### Пайплайн обработки запроса

[](#пайплайн-обработки-запроса)

```
HTTP POST /api/v{version}
    |
    v
ApiController
    |
    v
RequestRawDataHandler --- парсит HTTP-запрос (JSON body / query params)
    |
    v
BatchStrategyFactory --- определяет: одиночный или пакетный запрос
    |
    v
RequestHandler
    |--- Поиск MethodSpec по имени метода
    |--- Создание объекта Request из параметров
    |--- Валидация типизированных свойств
    |--- PreProcessors (если есть)
    |--- Method::call(Request) -> Response
    |--- PostProcessors (если есть)
    |
    v
ResponseService --- сериализация ответа в JSON-RPC 2.0 формат

```

### Структура проекта API-метода

[](#структура-проекта-api-метода)

```
src/RPC/V1/
    GetProductMethod.php          # Класс метода с #[JsonRPCAPI] атрибутом
    GetProduct/
        Request.php               # DTO входящего запроса
        Response.php              # DTO ответа

```

Классы, реализующие `ApiMethodInterface` и помеченные атрибутом `#[JsonRPCAPI]`, автоматически обнаруживаются и регистрируются бандлом.

---

Примеры
-------

[](#примеры)

ПримерОписаниеФайлы[Базовый](./docs/examples/base.md)Простейший пример создания API-методаRequest, Response, Method[Pre/Post-процессоры](./docs/examples/pre-and-post-processors.md)Выполнение логики до и после вызова методаRequest, Response, Method, AbstractMethod[Массив объектов](./docs/examples/array_of_objects.md)Возврат коллекции объектов в ответеRequest, Response, Method, Product[Бинарный ответ](./docs/examples/plain_response.md)Возврат изображений, документов и других бинарных данныхRequest, PlainResponse, Method---

Дополнительная документация
---------------------------

[](#дополнительная-документация)

РазделОписание[Обработка ошибок](./docs/errors.md)Коды ошибок, `JRPCException`, кастомные ошибки, `additionalInfo`[Notification-запросы](./docs/notifications.md)Запросы без `id`, параметр `strict_notifications`[Валидация параметров](./docs/validation.md)Автоматическая валидация типов, nullable, формат ошибок[Базовый класс JsonRpcRequest](./docs/json_rpc_request.md)Метод `toArray()`, рекурсивная сериализация[Partial updates (JSON Merge Patch)](./docs/partial_updates.md)`PartialRequestInterface`, `wasProvided()`, RFC 7396 семантика[Troubleshooting / FAQ](./docs/troubleshooting.md)Типичные проблемы и их решения[CHANGELOG](./CHANGELOG.md)История изменений по версиям---

Logging
-------

[](#logging)

Опциональная подсистема Request/Response логирования с маскировкой sensitive-данных через PSR-3 logger. По умолчанию выключена. Подробности — [docs/logging.md](docs/logging.md).

---

Partial updates (JSON Merge Patch)
----------------------------------

[](#partial-updates-json-merge-patch)

Бандл поддерживает PATCH-семантику по [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) для Update-методов, где клиент шлёт только изменённые поля.

**Проблема:** при стандартном паттерне `if ($request->getX() !== null) { $entity->setX($request->getX()); }` нельзя различить «поле не передано» и «поле передано как `null`» — оба случая дают `null` в DTO. Это значит, что нельзя **очистить** поле через PATCH.

**Решение:** Request DTO реализует `PartialRequestInterface`, и фреймворк отслеживает, какие поля реально пришли в payload. Сервис-слой использует `wasProvided('x')` вместо `!== null`:

```
use OV\JsonRPCAPIBundle\Core\Request\PartialUpdateRequest;

class UpdateUserRequest extends PartialUpdateRequest
{
    private ?int $id = null;
    private ?string $email = null;
    private ?string $bio = null;
    // getters/setters...
}
```

```
public function call(UpdateUserRequest $request): Response
{
    $user = $this->userRepository->find($request->getId());

    if ($request->wasProvided('email')) {
        $user->setEmail($request->getEmail()); // null = очистить
    }
    if ($request->wasProvided('bio')) {
        $user->setBio($request->getBio());
    }
    // ...
}
```

**Семантика payload-а:**

Payload`wasProvided`Поведение в сервисе`{"email": "new@x.com"}``true`установить новое значение`{"email": null}``true`очистить поле (`null`)`{}` (ключ отсутствует)`false`не трогать поле**Опт-ин:** только DTO, реализующие `PartialRequestInterface`, получают трекинг. Существующие DTO работают без изменений (полная обратная совместимость).

Подробности и edge-кейсы — в [docs/partial\_updates.md](./docs/partial_updates.md).

---

Версионирование API
-------------------

[](#версионирование-api)

Версия API определяется из URL (`/api/v1`, `/api/v2`) или явно через параметр `version` в атрибуте:

```
#[JsonRPCAPI(methodName: 'getProduct', type: 'POST', version: 2)]
```

Если `version` не указан, он извлекается из пространства имён класса (например, `App\RPC\V1` -&gt; версия 1).

---

Пакетные запросы (Batch)
------------------------

[](#пакетные-запросы-batch)

Бандл поддерживает пакетные JSON-RPC запросы согласно спецификации:

```
curl -X POST http://localhost/api/v1 \
  -H "Content-Type: application/json" \
  -d '[
    {"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
    {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
    {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}
  ]'
```

---

Pre- и Post-процессоры
----------------------

[](#pre--и-post-процессоры)

Процессоры позволяют выполнять логику до и после вызова метода API (логирование, аудит, уведомления и т.д.):

```
use OV\JsonRPCAPIBundle\Core\PreProcessorInterface;
use OV\JsonRPCAPIBundle\Core\PostProcessorInterface;

#[JsonRPCAPI(methodName: 'getProduct', type: 'POST')]
class GetProductMethod implements PreProcessorInterface, PostProcessorInterface
{
    public function getPreProcessors(): array
    {
        return [
            static::class => ['logRequest'],
        ];
    }

    public function getPostProcessors(): array
    {
        return [
            static::class => ['logResponse'],
        ];
    }

    public function logRequest(string $processorClass, ?object $request = null): void
    {
        // Вызывается ПЕРЕД call()
    }

    public function logResponse(string $processorClass, ?object $request = null, ?OvResponseInterface $response = null): void
    {
        // Вызывается ПОСЛЕ call()
    }

    public function call(Request $request): Response
    {
        // Основная логика
    }
}
```

Подробнее: [docs/examples/pre-and-post-processors.md](./docs/examples/pre-and-post-processors.md)

---

Swagger / OpenAPI
-----------------

[](#swagger--openapi)

### Генерация документации

[](#генерация-документации)

```
bin/console ov:swagger:generate
```

Генерирует файл `public/openapi/api_v1.yaml`, который можно использовать в Swagger UI.

### Аннотации для документации

[](#аннотации-для-документации)

**Скалярные свойства:**

```
use OV\JsonRPCAPIBundle\Core\Annotation\SwaggerProperty;

class Response
{
    #[SwaggerProperty(default: 'true', example: 'true')]
    private bool $success;

    #[SwaggerProperty(format: 'email', example: 'user@example.com')]
    private string $email;
}
```

**Массивы:**

```
use OV\JsonRPCAPIBundle\Core\Annotation\SwaggerArrayProperty;

class Response
{
    #[SwaggerArrayProperty(type: 'string')]
    private array $errors = [];

    #[SwaggerArrayProperty(type: Product::class, ofClass: true)]
    private array $products = [];
}
```

**Теги для группировки:**

```
#[JsonRPCAPI(methodName: 'getProduct', type: 'POST', tags: ['products'])]
```

Подробнее:

- [Теги](./docs/swagger/tags.md)
- [Скалярные свойства](./docs/swagger/scalar.md)
- [Массивы](./docs/swagger/array.md)

---

Безопасность
------------

[](#безопасность)

### Ролевой доступ

[](#ролевой-доступ)

Ограничение доступа к методам по ролям через атрибут `roles`:

```
#[JsonRPCAPI(
    methodName: 'deleteUser',
    type: 'POST',
    roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN']
)]
class DeleteUserMethod
{
    public function call(Request $request): Response { /* ... */ }
}
```

При отсутствии нужной роли бандл возвращает HTTP 403.

### Аутентификация

[](#аутентификация)

Бандл совместим с любым способом аутентификации Symfony:

- [JWT-токены через lexik/jwt-authentication-bundle](./docs/security/jwt_bundle.md)
- [Кастомная токенная аутентификация](./docs/security/self_made_token.md)
- [Ролевая модель](./docs/security/roles.md)

---

Тестирование
------------

[](#тестирование)

Запуск тестов:

```
./vendor/bin/phpunit tests/
```

Покрытие требует драйвера (xdebug или pcov). В `phpunit.xml.dist` уже настроен ``-блок для отчётов покрытия PHPUnit 10+.

Тестовый набор включает:

- **Unit-тесты** — все Core-компоненты, сервисы, модели запросов/ответов, DI, Swagger-модели.
- **Интеграционные тесты** — полный цикл обработки запросов через контроллер.
- **Тесты команд** — генерация Swagger YAML.
- **Security regression-тесты** (`tests/Security/`) — DoS-лимиты (payload, batch, DTO depth, array size), sanitization ошибок, CORS origin matching, проверки видимости сеттеров, path-containment команды.

Гайд по написанию тестов для собственных RPC-методов — [docs/testing.md](./docs/testing.md).

---

Конфигурация
------------

[](#конфигурация)

### Параметры `ov_json_rpc_api`

[](#параметры-ov_json_rpc_api)

ПараметрПо умолчаниюОписание`access_control_allow_origin_list``[]`Разрешённые CORS-домены. `['*']` — wildcard; список конкретных origin'ов матчится с заголовком запроса `Origin`.`cors_strict``true`При `true` — для origin'ов вне whitelist'а CORS-заголовок не отдаётся. При `false` — fallback к legacy comma-joined заголовку (невалидный, только для обратной совместимости).`strict_notifications``true`Строгое следование JSON-RPC 2.0 для Notification-запросов (без `id`). При `true` — сервер не возвращает ответ (по спеку). При `false` — лояльный режим: ответ возвращается, если результат непустой (поведение 3.x).`allow_extra_fields``false`При `false` — лишние поля в params, отсутствующие в Request DTO, вызывают `INVALID_PARAMS`. Можно переопределить per-method через `#[JsonRPCAPI(allowExtraFields: true)]`.`expose_internal_errors``false`При `false` (production-safe) — uncaught non-`JRPCException` исключения возвращаются клиенту как `Internal error.`, а оригинал пишется в LoggerInterface. При `true` — сырое сообщение отдаётся клиенту (для dev).`max_payload_bytes``1048576`Максимальный размер сырого тела запроса в байтах. Большие запросы — `INVALID_REQUEST`.`max_json_depth``64`Максимальная глубина вложенности JSON при декодировании. Глубже — `PARSE_ERROR`.`max_batch_size``50`Максимальное число запросов в одном JSON-RPC batch'е. Больше — единый `INVALID_REQUEST`.`max_dto_depth``10`Максимальная глубина рекурсии при гидратации вложенных Request DTO. Защита от stack/memory exhaustion.`max_array_param_size``1000`Максимальное число элементов массива-параметра, обрабатываемого через `addX()`-адеры.`swagger`—Конфигурация Swagger по версиям API`swagger.*.api_version``'1'`Номер версии API`swagger.*.base_path`—URL production-сервера`swagger.*.test_path``null`URL тестового сервера`swagger.*.base_path_variables``[]`Переменные для подстановки в base\_path`swagger.*.test_path_variables``[]`Переменные для подстановки в test\_path`swagger.*.auth_token_name`—Имя заголовка для токена авторизации`swagger.*.auth_token_test_value`—Тестовое значение токена`swagger.*.info`—Информация об API (title, description, contact, license)> **Security hardening:** рекомендации по значениям, обоснование и тюнинг для high-volume API — [docs/security\_hardening.md](./docs/security_hardening.md).

---

Параметры атрибута `#[JsonRPCAPI]`
----------------------------------

[](#параметры-атрибута-jsonrpcapi)

ПараметрТипОбязательныйПо умолчаниюОписание`methodName`stringда—Имя JSON-RPC метода`type`stringда—HTTP-метод (POST, GET, PUT, PATCH, DELETE)`version`?intнет`null`Версия API (если null — определяется из namespace)`summary`stringнет`''`Краткое описание для Swagger`description`stringнет`''`Подробное описание для Swagger`tags`?arrayнет`null`Теги для группировки в Swagger`roles`arrayнет`[]`Требуемые роли для доступа`ignoreInSwagger`boolнет`false`Исключить метод из Swagger-документации`group`?stringнет`null`Группа для пути в Swagger (например, `'products'` → `/products/get_product`)---

Коды ошибок JSON-RPC
--------------------

[](#коды-ошибок-json-rpc)

КодКонстантаОписание`-32700``PARSE_ERROR`Ошибка парсинга JSON`-32600``INVALID_REQUEST`Невалидный JSON-RPC запрос`-32601``METHOD_NOT_FOUND`Метод не найден`-32602``INVALID_PARAMS`Невалидные параметры`-32603``INTERNAL_ERROR`Внутренняя ошибка`-32000``SERVER_ERROR`Серверная ошибка---

Лицензия
--------

[](#лицензия)

[MIT](https://opensource.org/licenses/MIT)

Автор
-----

[](#автор)

Leonid Groshev —  — [otezvikentiy.tech](https://otezvikentiy.tech)

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance92

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor1

Top contributor holds 99.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~18 days

Recently: every ~9 days

Total

57

Last Release

39d ago

Major Versions

1.36 → 2.02025-09-01

2.18 → 3.22026-03-30

3.9 → 4.02026-05-11

PHP version history (2 changes)1.0.15PHP &gt;=8.1

2.0PHP &gt;=8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/19647948?v=4)[OtezVikentiy](/maintainers/OtezVikentiy)[@OtezVikentiy](https://github.com/OtezVikentiy)

---

Top Contributors

[![OtezVikentiy](https://avatars.githubusercontent.com/u/19647948?v=4)](https://github.com/OtezVikentiy "OtezVikentiy (124 commits)")[![andreybolonin](https://avatars.githubusercontent.com/u/2576509?v=4)](https://github.com/andreybolonin "andreybolonin (1 commits)")

---

Tags

apibundlejsonjsonrpcrpcsymfonyapisymfonybundleswaggerjsonrpc

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/otezvikentiy-json-rpc-api/health.svg)

```
[![Health](https://phpackages.com/badges/otezvikentiy-json-rpc-api/health.svg)](https://phpackages.com/packages/otezvikentiy-json-rpc-api)
```

###  Alternatives

[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M196](/packages/sulu-sulu)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.8M712](/packages/sylius-sylius)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9317.2k55](/packages/open-dxp-opendxp)[kimai/kimai

Kimai - Time Tracking

4.7k8.7k1](/packages/kimai-kimai)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

51390.8k2](/packages/web-auth-webauthn-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
