Work with JSON RPC in Symfony 4


Hello everyone, today we’ll talk about how to make friends Symfony 4, JSON RPC and OpenAPI 3.


This article is not intended for beginners, you should already understand how to work with Symfony, Depedency Injection and other "scary" things.


Today, let's look at one specific implementation of JSON RPC.


Implementations


There are many JSON RPC implementations for Symfony, in particular:



We’ll talk about the latter in this article. This library has several advantages that determined my choice.


It was developed without binding to any framework ( yoanm / php-jsonrpc-server-sdk ), there is a bundle for Symfony, it has several additional packages that allow you to add incoming data checking, automatic documentation, events and interfaces to be able to complement the work without redefinition.


Installation


To get started, install symfony / skeleton.


$ composer create-project symfony/skeleton jsonrpc

Go to the project folder.


$ cd jsonrpc

And install the necessary library.


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

Customizable.


// config/bundles.php
return [
    ...
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Yoanm\SymfonyJsonRpcHttpServer\JsonRpcHttpServerBundle::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: ~

Add a service that will store all our methods.


// src/MappingCollector.php
mappingList[$methodName] = $method;
   }
   /**
    * @return JsonRpcMethodInterface[]
    */
   public function getMappingList() : array
   {
       return $this->mappingList;
   }
}

And add the service to services.yaml.


# config/services.yaml
services:
    ...
    mapping_aware_service:
        class: App\MappingCollector
        tags: ['json_rpc_http_server.method_aware']
    ...

Method Implementation


RPC JSON methods are added as regular services in the services.yaml file. We first implement the ping method itself.


// src/Method/PingMethod.php

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


# config/services.yaml
services:
    ...
    App\Method\PingMethod:
        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

# config/services.yaml
services:
    ...
    App\Method\ParamsMethod:
   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 [
    ...
    Yoanm\SymfonyJsonRpcParamsValidator\JsonRpcParamsValidatorBundle::class => ['all' => true],
    ...
];

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


// src/Method/ParamsMethod.php
 [
           '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 [
    ...
    Yoanm\SymfonyJsonRpcHttpServerDoc\JsonRpcHttpServerDocBundle::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'

Ответ
{
  "methods": [
    {
      "identifier": "Params",
      "name": "params"
    },
    {
      "identifier": "Ping",
      "name": "ping"
    }
  ],
  "errors": [
    {
      "id": "ParseError-32700",
      "title": "Parse error",
      "type": "object",
      "properties": {
        "code": -32700
      }
    },
    {
      "id": "InvalidRequest-32600",
      "title": "Invalid request",
      "type": "object",
      "properties": {
        "code": -32600
      }
    },
    {
      "id": "MethodNotFound-32601",
      "title": "Method not found",
      "type": "object",
      "properties": {
        "code": -32601
      }
    },
    {
      "id": "ParamsValidationsError-32602",
      "title": "Params validations error",
      "type": "object",
      "properties": {
        "code": -32602,
        "data": {
          "type": "object",
          "nullable": true,
          "required": true,
          "siblings": {
            "violations": {
              "type": "array",
              "nullable": true,
              "required": false
            }
          }
        }
      }
    },
    {
      "id": "InternalError-32603",
      "title": "Internal error",
      "type": "object",
      "properties": {
        "code": -32603,
        "data": {
          "type": "object",
          "nullable": true,
          "required": false,
          "siblings": {
            "previous": {
              "description": "Previous error message",
              "type": "string",
              "nullable": true,
              "required": false
            }
          }
        }
      }
    }
  ],
  "http": {
    "host": "127.0.0.1:8000"
  }
}

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


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

// config/bundles.php
return [
    ...
    Yoanm\SymfonyJsonRpcParamsSfConstraintsDoc\JsonRpcParamsSfConstraintsDocBundle::class => ['all' => true],
    ...
];

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


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

Ответ
{
  "methods": [
    {
      "identifier": "Params",
      "name": "params",
      "params": {
        "type": "object",
        "nullable": false,
        "required": true,
        "siblings": {
          "name": {
            "type": "string",
            "nullable": true,
            "required": true,
            "minLength": 1,
            "maxLength": 32
          },
          "age": {
            "type": "string",
            "nullable": true,
            "required": true
          },
          "sex": {
            "type": "string",
            "nullable": true,
            "required": false,
            "allowedValues": [
              "f",
              "m"
            ]
          }
        }
      }
    },
    {
      "identifier": "Ping",
      "name": "ping"
    }
  ],
  "errors": [
    {
      "id": "ParseError-32700",
      "title": "Parse error",
      "type": "object",
      "properties": {
        "code": -32700
      }
    },
    {
      "id": "InvalidRequest-32600",
      "title": "Invalid request",
      "type": "object",
      "properties": {
        "code": -32600
      }
    },
    {
      "id": "MethodNotFound-32601",
      "title": "Method not found",
      "type": "object",
      "properties": {
        "code": -32601
      }
    },
    {
      "id": "ParamsValidationsError-32602",
      "title": "Params validations error",
      "type": "object",
      "properties": {
        "code": -32602,
        "data": {
          "type": "object",
          "nullable": true,
          "required": true,
          "siblings": {
            "violations": {
              "type": "array",
              "nullable": true,
              "required": false,
              "item_validation": {
                "type": "object",
                "nullable": true,
                "required": true,
                "siblings": {
                  "path": {
                    "type": "string",
                    "nullable": true,
                    "required": true,
                    "example": "[key]"
                  },
                  "message": {
                    "type": "string",
                    "nullable": true,
                    "required": true
                  },
                  "code": {
                    "type": "string",
                    "nullable": true,
                    "required": false
                  }
                }
              }
            }
          }
        }
      }
    },
    {
      "id": "InternalError-32603",
      "title": "Internal error",
      "type": "object",
      "properties": {
        "code": -32603,
        "data": {
          "type": "object",
          "nullable": true,
          "required": false,
          "siblings": {
            "previous": {
              "description": "Previous error message",
              "type": "string",
              "nullable": true,
              "required": false
            }
          }
        }
      }
    }
  ],
  "http": {
    "host": "127.0.0.1:8000"
  }
}

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 [
    ...
    Yoanm\SymfonyJsonRpcHttpServerOpenAPIDoc\JsonRpcHttpServerOpenAPIDocBundle::class => ['all' => true],
    ...
];

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


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

Ответ
{
  "openapi": "3.0.0",
  "servers": [
    {
      "url": "http:\/\/127.0.0.1:8000"
    }
  ],
  "paths": {
    "\/Params\/..\/json-rpc": {
      "post": {
        "summary": "\"params\" json-rpc method",
        "operationId": "Params",
        "requestBody": {
          "required": true,
          "content": {
            "application\/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "required": [
                      "jsonrpc",
                      "method"
                    ],
                    "properties": {
                      "id": {
                        "example": "req_id",
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ]
                      },
                      "jsonrpc": {
                        "type": "string",
                        "example": "2.0"
                      },
                      "method": {
                        "type": "string"
                      },
                      "params": {
                        "title": "Method parameters"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "params"
                    ],
                    "properties": {
                      "params": {
                        "$ref": "#\/components\/schemas\/Method-Params-RequestParams"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "method": {
                        "example": "params"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application\/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "required": [
                        "jsonrpc"
                      ],
                      "properties": {
                        "id": {
                          "example": "req_id",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            }
                          ]
                        },
                        "jsonrpc": {
                          "type": "string",
                          "example": "2.0"
                        },
                        "result": {
                          "title": "Result"
                        },
                        "error": {
                          "title": "Error"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "result": {
                          "description": "Method result"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "oneOf": [
                            {
                              "$ref": "#\/components\/schemas\/ServerError-ParseError-32700"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-InvalidRequest-32600"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-MethodNotFound-32601"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-ParamsValidationsError-32602"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-InternalError-32603"
                            }
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "\/Ping\/..\/json-rpc": {
      "post": {
        "summary": "\"ping\" json-rpc method",
        "operationId": "Ping",
        "requestBody": {
          "required": true,
          "content": {
            "application\/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "required": [
                      "jsonrpc",
                      "method"
                    ],
                    "properties": {
                      "id": {
                        "example": "req_id",
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ]
                      },
                      "jsonrpc": {
                        "type": "string",
                        "example": "2.0"
                      },
                      "method": {
                        "type": "string"
                      },
                      "params": {
                        "title": "Method parameters"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "method": {
                        "example": "ping"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application\/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "required": [
                        "jsonrpc"
                      ],
                      "properties": {
                        "id": {
                          "example": "req_id",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            }
                          ]
                        },
                        "jsonrpc": {
                          "type": "string",
                          "example": "2.0"
                        },
                        "result": {
                          "title": "Result"
                        },
                        "error": {
                          "title": "Error"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "result": {
                          "description": "Method result"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "oneOf": [
                            {
                              "$ref": "#\/components\/schemas\/ServerError-ParseError-32700"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-InvalidRequest-32600"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-MethodNotFound-32601"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-ParamsValidationsError-32602"
                            },
                            {
                              "$ref": "#\/components\/schemas\/ServerError-InternalError-32603"
                            }
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Method-Params-RequestParams": {
        "type": "object",
        "nullable": false,
        "required": [
          "name",
          "age"
        ],
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "minLength": 1,
            "maxLength": 32
          },
          "age": {
            "type": "string",
            "nullable": true
          },
          "sex": {
            "type": "string",
            "nullable": true,
            "enum": [
              "f",
              "m"
            ]
          }
        }
      },
      "ServerError-ParseError-32700": {
        "title": "Parse error",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "example": -32700
              }
            }
          }
        ]
      },
      "ServerError-InvalidRequest-32600": {
        "title": "Invalid request",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "example": -32600
              }
            }
          }
        ]
      },
      "ServerError-MethodNotFound-32601": {
        "title": "Method not found",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "example": -32601
              }
            }
          }
        ]
      },
      "ServerError-ParamsValidationsError-32602": {
        "title": "Params validations error",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code",
              "data"
            ],
            "properties": {
              "code": {
                "example": -32602
              },
              "data": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "violations": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "object",
                      "nullable": true,
                      "required": [
                        "path",
                        "message"
                      ],
                      "properties": {
                        "path": {
                          "type": "string",
                          "nullable": true,
                          "example": "[key]"
                        },
                        "message": {
                          "type": "string",
                          "nullable": true
                        },
                        "code": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "ServerError-InternalError-32603": {
        "title": "Internal error",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "example": -32603
              },
              "data": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "previous": {
                    "description": "Previous error message",
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}

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


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


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


# config/services.yaml
services:
    ...
    App\Listener\MethodDocListener:
        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
getMethod();
        if ($method instanceof JsonRpcMethodWithDocInterface) {
            $doc = $event->getDoc();
            $doc->setResultDoc($method->getDocResponse());
            foreach ($method->getDocErrors() as $error) {
                if ($error instanceof ErrorDoc) {
                    $doc->addCustomError($error);
                }
            }
            $doc->setDescription($method->getDocDescription());
            $doc->addTag($method->getDocTag());
        }
    }
    public function enhanceDoc(OpenAPIDocCreatedEvent $event)
    {
        $doc = $event->getOpenAPIDoc();
        $doc['info'] = [
            'title' => 'Main title',
            'version' => '1.0.0',
            'description' => 'Main description'
        ];
        $event->setOpenAPIDoc($doc);
    }
}

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


// src/Domain/JsonRpcMethodWithDocInterface.php

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


// src/Method/UserMethod.php
 $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();
        $response->setNullable(false);
        $response->addSibling((new StringDoc())
            ->setNullable(false)
            ->setDescription('Name of user')
            ->setName('name')
        );
        $response->addSibling((new NumberDoc())
            ->setNullable(false)
            ->setDescription('Age of user')
            ->setName('age')
        );
        $response->addSibling((new StringDoc())
            ->setNullable(true)
            ->setDescription('Sex of user')
            ->setName('sex')
        );
        return $response;
    }
}

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


services:
    ...
    App\Method\UserMethod:
        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'

Ответ
{
  "openapi": "3.0.0",
  "servers": [
    {
      "url": "http:\/\/127.0.0.1:8000"
    }
  ],
  "paths": {
    ...
    "\/User\/..\/json-rpc": {
      "post": {
        "summary": "\"user\" json-rpc method",
        "description": "User method",
        "tags": [
          "main"
        ],
        ...        
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application\/json": {
                "schema": {
                  "allOf": [
                    ...
                    {
                      "type": "object",
                      "properties": {
                        "result": {
                          "$ref": "#\/components\/schemas\/Method-User-Result"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "oneOf": [
                            {
                              "$ref": "#\/components\/schemas\/Error-Error11"
                            },
                            ...
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      ...
      "Method-User-Result": {
        "type": "object",
        "nullable": false,
        "properties": {
          "name": {
            "description": "Name of user",
            "type": "string",
            "nullable": false
          },
          "age": {
            "description": "Age of user",
            "type": "number",
            "nullable": false
          },
          "sex": {
            "description": "Sex of user",
            "type": "string",
            "nullable": true
          }
        }
      },
      "Error-Error11": {
        "title": "Error 1",
        "allOf": [
          {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "number"
              },
              "message": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "example": 1
              }
            }
          }
        ]
      },
      ...
    }
  },
  "info": {
    "title": "Main title",
    "version": "1.0.0",
    "description": "Main description"
  }
}

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


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



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


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


    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
        },
        "post-install-cmd": [
            "HarmBandstra\\SwaggerUiBundle\\Composer\\ScriptHandler::linkAssets",
            "@auto-scripts"
        ],
        "post-update-cmd": [
            "HarmBandstra\\SwaggerUiBundle\\Composer\\ScriptHandler::linkAssets",
            "@auto-scripts"
        ]
    },

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


$ composer require harmbandstra/swagger-ui-bundle

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


// config/bundles.php
 ['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 и автоматическую документацию OpenAPI с визуализацией с помощью Swagger UI.


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


Also popular now: