Работа с JSON RPC в Symfony 4

Автор: admin от 26-06-2019, 21:55, посмотрело: 344

Работа с JSON RPC в Symfony 4

Всем привет, сегодня поговорим о том, как подружить Symfony 4, JSON RPC и OpenAPI 3.



Данная статья рассчитана не на новичков, вы уже должны понимать как работать с Symfony, Depedency Injection и другими «страшными» вещами.



Сегодня рассмотрим одну конкретную реализацию JSON RPC.

wasinger/jsonrpc-bundle
  • timiki/rpc-server-bundle

  • NeoFusion/JsonRpcBundle

  • insidestyles/json-rpc-bundle

  • symfony-jsonrpc-http-server



  • О последней как раз и поговорим в данной статье. Данная библиотека несколько преимуществ, которые определили мой выбор.



    Она разработана без привязки к какому либо фреймворку (yoanm/php-jsonrpc-server-sdk), есть бандл для Symfony, имеет несколько дополнительных пакетов, позволяющие добавить проверку входящих данных, автоматическую документацию, события и интерфейсы для возможности дополнить работу без переопределения.



    Установка



    Для начала устанавливаем symfony/skeleton.



    $ composer create-project symfony/skeleton jsonrpc


    Переходим в папку проекта.



    $ cd jsonrpc


    И устанавливаем необходимую библиотеку.



    $ composer require yoanm/symfony-jsonrpc-http-server


    Настраиваем.



    // config/bundles.php
    return [
        ...
        SymfonyBundleFrameworkBundleFrameworkBundle::class => ['all' => true],
        YoanmSymfonyJsonRpcHttpServerJsonRpcHttpServerBundle::class => ['all' => true],
        ...
    ];


    # config/routes.yaml
    json-rpc-endpoint:
        resource: '@JsonRpcHttpServerBundle/Resources/config/routing/endpoint.xml'


    # config/packages/json_rpc.yaml
    json_rpc_http_server: ~


    Добавляем сервис, который будет хранить все наши методы.



    // src/MappingCollector.php
    <?php
    
    namespace App;
    
    use YoanmJsonRpcServerDomainJsonRpcMethodAwareInterface;
    use YoanmJsonRpcServerDomainJsonRpcMethodInterface;
    
    class MappingCollector implements JsonRpcMethodAwareInterface
    {
       /** @var JsonRpcMethodInterface[] */
       private $mappingList = [];
    
       public function addJsonRpcMethod(string $methodName, JsonRpcMethodInterface $method): void
       {
           $thismappingList[$methodName] = $method;
       }
    
       /**
        * @return JsonRpcMethodInterface[]
        */
       public function getMappingList() : array
       {
           return $thismappingList;
       }
    }


    И добавляем сервис в services.yaml.



    # config/services.yaml
    services:
        ...
        mapping_aware_service:
            class: AppMappingCollector
            tags: ['json_rpc_http_server.method_aware']
        ...


    Реализация методов



    Методы JSON RPC добавляются как обычные сервисы в файле services.yaml. Реализуем сначала сам метод ping.



    // src/Method/PingMethod.php
    <?php
    
    namespace AppMethod;
    
    use YoanmJsonRpcServerDomainJsonRpcMethodInterface;
    
    class PingMethod implements JsonRpcMethodInterface
    {
       public function apply(array $paramList = null)
       {
           return 'pong';
       }
    }


    И добавим как сервис.



    # config/services.yaml
    services:
        ...
        AppMethodPingMethod:
            public: false
            tags: [{ method: 'ping', name: 'json_rpc_http_server.jsonrpc_method' }]
        ...


    Запускаем встроенный веб сервер Symfony.



    $ symfony serve


    Пробуем сделать вызов.



    $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"ping","params":[],"id" : 1 }]'


    [
      {
        "jsonrpc": "2.0",
        "id": 1,
        "result": "pong"
      }
    ]


    Теперь реализуем метод, получающий параметры. В качестве ответа вернем входные данные.



    // src/Method/ParamsMethod.php
    <?php
    
    namespace AppMethod;
    
    use YoanmJsonRpcServerDomainJsonRpcMethodInterface;
    
    class ParamsMethod implements JsonRpcMethodInterface
    {
       public function apply(array $paramList = null)
       {
           return $paramList;
       }
    }


    # config/services.yaml
    services:
        ...
        AppMethodParamsMethod:
       public: false
       tags: [{ method: 'params', name: 'json_rpc_http_server.jsonrpc_method' }]
        ...


    Пробуем вызвать.



    $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"params","params":{"name":"John","age":21},"id" : 1 }]'


    [
      {
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
          "name": "John",
          "age": 21
        }
      }
    ]


    Валидация входных данных метода



    Если требуется автоматическая проверка данных на входе метода, то на этот случай есть пакет yoanm/symfony-jsonrpc-params-validator.



    $ composer require yoanm/symfony-jsonrpc-params-validator


    Подключаем бандл.



    // config/bundles.php
    return [
        ...
        YoanmJsonRpcParamsValidatorBundleJsonRpcParamsValidatorBundle::class => ['all' => true],
        ...
    ];


    Методы, которые нуждаются в проверке входных данных должны реализовать интерфейс YoanmJsonRpcParamsSymfonyValidatorDomainMethodWithValidatedParamsInterface. Изменим немного класс ParamsMethod.



    // src/Method/ParamsMethod.php
    <?php
    
    namespace AppMethod;
    
    use SymfonyComponentValidatorConstraint;
    use SymfonyComponentValidatorConstraintsChoice;
    use SymfonyComponentValidatorConstraintsCollection;
    use SymfonyComponentValidatorConstraintsLength;
    use SymfonyComponentValidatorConstraintsNotBlank;
    use SymfonyComponentValidatorConstraintsOptional;
    use SymfonyComponentValidatorConstraintsPositive;
    use SymfonyComponentValidatorConstraintsRequired;
    use YoanmJsonRpcParamsSymfonyValidatorDomainMethodWithValidatedParamsInterface;
    use YoanmJsonRpcServerDomainJsonRpcMethodInterface;
    
    class ParamsMethod implements JsonRpcMethodInterface, MethodWithValidatedParamsInterface
    {
       public function apply(array $paramList = null)
    
           return $paramList;
       }
    
       public function getParamsConstraint() : Constraint
    
           return new Collection(['fields' => [
               'name' => new Required([
                   new Length(['min' => 1, 'max' => 32])
               ]),
               'age' => new Required([
                   new Positive()
               ]),
               'sex' => new Optional([
                   new Choice(['f', 'm'])
               ]),
           ]]);
       }
    }


    Теперь если выполним запрос с пустыми параметрами или с ошибками, то получим в ответ соответствующие ошибки.



    $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{"jsonrpc":"2.0","method":"params","params":[],"id" : 1 }]'


    [
      {
        "jsonrpc": "2.0",
        "id": 1,
        "error": {
          "code": -32602,
          "message": "Invalid params",
          "data": {
            "violations": [
              {
                "path": "[name]",
                "message": "This field is missing.",
                "code": "2fa2158c-2a7f-484b-98aa-975522539ff8"
              },
              {
                "path": "[age]",
                "message": "This field is missing.",
                "code": "2fa2158c-2a7f-484b-98aa-975522539ff8"
              }
            ]
          }
        }
      }
    ]


    $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{"jsonrpc":"2.0","method":"params","params":{"name":"John","age":-1},"id" : 1 }]'


    [
      {
        "jsonrpc": "2.0",
        "id": 1,
        "error": {
          "code": -32602,
          "message": "Invalid params",
          "data": {
            "violations": [
              {
                "path": "[age]",
                "message": "This value should be positive.",
                "code": "778b7ae0-84d3-481a-9dec-35fdb64b1d78"
              }
            ]
          }
        }
      }


    $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"params","params":{"name":"John","age":21,"sex":"u"},"id" : 1 }]'  


    [
      {
        "jsonrpc": "2.0",
        "id": 1,
        "error": {
          "code": -32602,
          "message": "Invalid params",
          "data": {
            "violations": [
              {
                "path": "[sex]",
                "message": "The value you selected is not a valid choice.",
                "code": "8e179f1b-97aa-4560-a02f-2a8b42e49df7"
              }
            ]
          }
        }
      }
    ]


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



    Устанавливаем дополнительный пакет.



    composer require yoanm/symfony-jsonrpc-http-server-doc


    Настраиваем бандл.



    // config/bundles.php
    return [
        ...
        YoanmSymfonyJsonRpcHttpServerDocJsonRpcHttpServerDocBundle::class => ['all' => true],
        ...
    ];


    # config/routes.yaml
    ...
    json-rpc-endpoint-doc:
      resource: '@JsonRpcHttpServerDocBundle/Resources/config/routing/endpoint.xml'


    # config/packages/json_rpc.yaml
    ...
    json_rpc_http_server_doc: ~


    Теперь можно получить документацию в JSON формате.



    $ curl 'http://127.0.0.1:8000/doc'




    Но как же так? А где описание входных параметров? Для этого нужно поставить еще один бандл yoanm/symfony-jsonrpc-params-sf-constraints-doc.



    $ composer require yoanm/symfony-jsonrpc-params-sf-constraints-doc


    // config/bundles.php
    return [
        ...
        YoanmSymfonyJsonRpcParamsSfConstraintsDocJsonRpcParamsSfConstraintsDocBundle::class => ['all' => true],
        ...
    ];


    Теперь если сделать запрос, то получим JSON уже методы с параметрами.



    $ curl 'http://127.0.0.1:8000/doc'




    OpenAPI 3



    Для того, чтобы JSON документация была совместима со стандартом OpenAPI 3, нужно установить yoanm/symfony-jsonrpc-http-server-openapi-doc.



    $ composer require yoanm/symfony-jsonrpc-http-server-openapi-doc


    Настраиваем.



    // config/bundles.php
    return [
        ...
        YoanmSymfonyJsonRpcHttpServerOpenAPIDocJsonRpcHttpServerOpenAPIDocBundle::class => ['all' => true],
        ...
    ];


    Сделав новый запрос, мы получим JSON документацию в формате OpenApi 3.



    $ curl 'http://127.0.0.1:8000/doc/openapi.json'




    Документация ответа метода



    Штатного функционала (например путем реализации интерфейса), позволяющего добавлять ответы методов в документацию, нет. Но есть возможность, путем подписки на события, добавить нужную информацию самостоятельно.



    Добавляем слушателя.



    # config/services.yaml
    services:
        ...
        AppListenerMethodDocListener:
            tags:
                - name: 'kernel.event_listener'
                  event: 'json_rpc_http_server_doc.method_doc_created'
                  method: 'enhanceMethodDoc'
                - name: 'kernel.event_listener'
                  event: 'json_rpc_http_server_openapi_doc.array_created'
                  method: 'enhanceDoc'
        ...


    // src/Listener/MethodDocListener.php
    <?php
    
    namespace AppListener;
    
    use AppDomainJsonRpcMethodWithDocInterface;
    use YoanmJsonRpcServerDocDomainModelErrorDoc;
    use YoanmSymfonyJsonRpcHttpServerDocEventMethodDocCreatedEvent;
    use YoanmSymfonyJsonRpcHttpServerOpenAPIDocEventOpenAPIDocCreatedEvent;
    
    class MethodDocListener
    {
        public function enhanceMethodDoc(MethodDocCreatedEvent  $event) : void
        {
            $method = $eventgetMethod();
    
            if ($method instanceof JsonRpcMethodWithDocInterface) {
                $doc = $eventgetDoc();
                $docsetResultDoc($methodgetDocResponse());
    
                foreach ($methodgetDocErrors() as $error) {
                    if ($error instanceof ErrorDoc) {
                        $docaddCustomError($error);
                    }
                }
    
                $docsetDescription($methodgetDocDescription());
                $docaddTag($methodgetDocTag());
            }
        }
    
        public function enhanceDoc(OpenAPIDocCreatedEvent $event)
        {
            $doc = $eventgetOpenAPIDoc();
    
            $doc['info'] = [
                'title' => 'Main title',
                'version' => '1.0.0',
                'description' => 'Main description'
            ];
    
            $eventsetOpenAPIDoc($doc);
        }
    }


    Еще, для того, чтобы прямо в слушателе не описывать документацию методов, сделаем интерфейс, который должны будут реализовывать сами методы.



    // src/Domain/JsonRpcMethodWithDocInterface.php
    <?php
    
    namespace AppDomain;
    
    use YoanmJsonRpcServerDocDomainModelErrorDoc;
    use YoanmJsonRpcServerDocDomainModelTypeTypeDoc;
    
    interface JsonRpcMethodWithDocInterface
    {
        /**
         * @return TypeDoc
         */
        public function getDocResponse(): TypeDoc;
    
        /**
         * @return ErrorDoc[]
         */
        public function getDocErrors(): array;
    
        /**
         * @return string
         */
        public function getDocDescription(): string;
    
        /**
         * @return string
         */
        public function getDocTag(): string;
    }


    Теперь добавим новый метод, который будет в себе содержать нужную информацию.



    // src/Method/UserMethod.php
    <?php
    
    namespace AppMethod;
    
    use AppDomainJsonRpcMethodWithDocInterface;
    use SymfonyComponentValidatorConstraint;
    use SymfonyComponentValidatorConstraintsChoice;
    use SymfonyComponentValidatorConstraintsCollection;
    use SymfonyComponentValidatorConstraintsLength;
    use SymfonyComponentValidatorConstraintsNotBlank;
    use SymfonyComponentValidatorConstraintsOptional;
    use SymfonyComponentValidatorConstraintsPositive;
    use SymfonyComponentValidatorConstraintsRequired;
    use YoanmJsonRpcParamsSymfonyValidatorDomainMethodWithValidatedParamsInterface;
    use YoanmJsonRpcServerDomainJsonRpcMethodInterface;
    use YoanmJsonRpcServerDocDomainModelErrorDoc;
    use YoanmJsonRpcServerDocDomainModelTypeArrayDoc;
    use YoanmJsonRpcServerDocDomainModelTypeNumberDoc;
    use YoanmJsonRpcServerDocDomainModelTypeObjectDoc;
    use YoanmJsonRpcServerDocDomainModelTypeStringDoc;
    use YoanmJsonRpcServerDocDomainModelTypeTypeDoc;
    
    class UserMethod implements JsonRpcMethodInterface, MethodWithValidatedParamsInterface, JsonRpcMethodWithDocInterface
    {
        public function apply(array $paramList = null)
        {
            return [
                'name' => $paramList['name'],
                'age' => $paramList['age'],
                'sex' => $paramList['sex'] ?? null,
            ];
        }
    
        public function getParamsConstraint() : Constraint
        {
            return new Collection(['fields' => [
                'name' => new Required([
                    new Length(['min' => 1, 'max' => 32])
                ]),
                'age' => new Required([
                    new Positive()
                ]),
                'sex' => new Optional([
                    new Choice(['f', 'm'])
                ]),
            ]]);
        }
    
        public function getDocDescription(): string
        {
            return 'User method';
        }
    
        public function getDocTag(): string
        {
            return 'main';
        }
    
        public function getDocErrors(): array
        {
            return [new ErrorDoc('Error 1', 1)];
        }
    
        public function getDocResponse(): TypeDoc
        {
            $response = new ObjectDoc();
            $responsesetNullable(false);
    
            $responseaddSibling((new StringDoc())
                setNullable(false)
                setDescription('Name of user')
                setName('name')
            );
    
            $responseaddSibling((new NumberDoc())
                setNullable(false)
                setDescription('Age of user')
                setName('age')
            );
    
            $responseaddSibling((new StringDoc())
                setNullable(true)
                setDescription('Sex of user')
                setName('sex')
            );
    
            return $response;
        }
    }


    Не забываем прописать новый сервис.



    services:
        ...
        AppMethodUserMethod:
            public: false
            tags: [{ method: 'user', name: 'json_rpc_http_server.jsonrpc_method' }]
        ...


    Теперь сделав новый запрос к /doc/openapi.json, получим новые данные.



    curl 'http://127.0.0.1:8000/doc/openapi.json'




    Визуализация JSON документации



    JSON это круто, но люди обычно хотят видеть более человечный результат. Файл /doc/openapi.json можно отдать внешним сервисам визуализации, например Swagger Editor.



    Работа с JSON RPC в Symfony 4



    При желании можно установить Swagger UI и в нашем проекте. Воспользуемся пакетом harmbandstra/swagger-ui-bundle.



    Для корректной публикации ресурсов добавляем с composer.json следующее.



        "scripts": {
            "auto-scripts": {
                "cache:clear": "symfony-cmd",
                "assets:install %PUBLIC_DIR%": "symfony-cmd"
            },
            "post-install-cmd": [
                "HarmBandstraSwaggerUiBundleComposerScriptHandler::linkAssets",
                "@auto-scripts"
            ],
            "post-update-cmd": [
                "HarmBandstraSwaggerUiBundleComposerScriptHandler::linkAssets",
                "@auto-scripts"
            ]
        },


    После ставим пакет.



    $ composer require harmbandstra/swagger-ui-bundle


    Подключаем бандл.



    // config/bundles.php
    <?php
    
    return [
        // ...
        HarmBandstraSwaggerUiBundleHBSwaggerUiBundle::class => ['dev' => true]
    ];


    # config/routes.yaml
    _swagger-ui:
        resource: '@HBSwaggerUiBundle/Resources/config/routing.yml'
        prefix: /docs


    # config/packages/hb_swagger_ui.yaml
    hb_swagger_ui:
      directory: "http://127.0.0.1:8000"
      files:
        - "/doc/openapi.json"


    Теперь перейдя по ссылке http://127.0.0.1:8000/docs/ получим документацию в красивом виде.



    Работа с JSON RPC в Symfony 4



    Итоги



    В результате все проведенных манипуляций мы получили работающий JSON RPC на базе Symfony 4 и автоматическую документацию OpenAPI с визуализацией с помощью Swagger UI.



    Всем спасибо.



    Источник: Хабр / Интересные публикации

    Категория: Программирование

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *