
Halo semuanya, hari ini kita akan berbicara tentang cara mencari teman Symfony 4, JSON RPC dan OpenAPI 3.
Artikel ini tidak ditujukan untuk pemula, Anda harus sudah memahami cara bekerja dengan Symfony, Depedency Injection dan hal-hal "menakutkan" lainnya.
Hari ini, mari kita lihat satu implementasi spesifik JSON RPC.
Implementasi
Ada banyak implementasi JSON RPC untuk Symfony, khususnya:
Kami akan berbicara tentang yang terakhir dalam artikel ini. Perpustakaan ini memiliki beberapa keunggulan yang menentukan pilihan saya.
Ini dikembangkan tanpa mengikat kerangka kerja apa pun ( yoanm / php-jsonrpc-server-sdk ), ada bundel untuk Symfony, ia memiliki beberapa paket tambahan yang memungkinkan Anda untuk menambahkan pemeriksaan input, dokumentasi otomatis, acara dan antarmuka untuk dapat melengkapi pekerjaan tanpa redefinisi.
Instalasi
Untuk memulai, instal symfony / skeleton.
$ composer create-project symfony/skeleton jsonrpc
Buka folder proyek.
$ cd jsonrpc
Dan instal perpustakaan yang diperlukan.
$ composer require yoanm/symfony-jsonrpc-http-server
Dapat disesuaikan
# config/routes.yaml json-rpc-endpoint: resource: '@JsonRpcHttpServerBundle/Resources/config/routing/endpoint.xml'
# config/packages/json_rpc.yaml json_rpc_http_server: ~
Tambahkan layanan yang akan menyimpan semua metode kami.
Dan tambahkan layanan ke services.yaml.
# config/services.yaml services: ... mapping_aware_service: class: App\MappingCollector tags: ['json_rpc_http_server.method_aware'] ...
Implementasi Metode
Metode RPC JSON ditambahkan sebagai layanan reguler di file services.yaml. Kami pertama-tama mengimplementasikan metode ping itu sendiri.
Dan tambahkan sebagai layanan.
# config/services.yaml services: ... App\Method\PingMethod: public: false tags: [{ method: 'ping', name: 'json_rpc_http_server.jsonrpc_method' }] ...
Kami meluncurkan server web built-in Symfony.
$ symfony serve
Kami mencoba menelepon.
$ 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" } ]
Sekarang kami menerapkan metode yang menerima parameter. Kami akan mengembalikan data input sebagai jawaban.
# config/services.yaml services: ... App\Method\ParamsMethod: public: false tags: [{ method: 'params', name: 'json_rpc_http_server.jsonrpc_method' }] ...
Mencoba menelepon.
$ 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 } } ]
Metode validasi input
Jika verifikasi otomatis data pada input metode diperlukan, maka untuk kasus ini ada paket yoanm / symfony-jsonrpc-params-validator .
$ composer require yoanm/symfony-jsonrpc-params-validator
Hubungkan bundel.
Metode yang perlu memverifikasi input harus mengimplementasikan antarmuka Yoanm \ JsonRpcParamsSymfonyValidator \ Domain \ MethodWithValidatedParamsInterface . Mari kita sedikit memodifikasi kelas ParamsMethod .
Sekarang jika kami menjalankan permintaan dengan parameter kosong atau dengan kesalahan, kami akan menerima kesalahan yang sesuai sebagai respons.
$ 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" } ] } } } ]
Dokumentasi Otomatis
Instal paket tambahan.
composer require yoanm/symfony-jsonrpc-http-server-doc
Kami mengonfigurasi bundel.
# config/routes.yaml ... json-rpc-endpoint-doc: resource: '@JsonRpcHttpServerDocBundle/Resources/config/routing/endpoint.xml'
# config/packages/json_rpc.yaml ... json_rpc_http_server_doc: ~
Sekarang Anda bisa mendapatkan dokumentasi dalam format JSON.
$ curl 'http://127.0.0.1:8000/doc'
Jawabannya { "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" } }
Tapi bagaimana caranya? Dan di mana deskripsi parameter input? Untuk melakukan ini, letakkan bundel lain yoanm / symfony-jsonrpc-params-sf-kendala-doc .
$ composer require yoanm/symfony-jsonrpc-params-sf-constraints-doc
Sekarang jika kita membuat permintaan, kita akan mendapatkan metode JSON dengan parameter.
$ curl 'http://127.0.0.1:8000/doc'
Jawabannya { "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
Agar dokumentasi JSON kompatibel dengan standar OpenAPI 3, Anda perlu menginstal yoanm / symfony-jsonrpc-http-server-openapi-doc .
$ composer require yoanm/symfony-jsonrpc-http-server-openapi-doc
Dapat disesuaikan
Setelah membuat permintaan baru, kami akan menerima dokumentasi JSON dalam format OpenApi 3.
$ curl 'http://127.0.0.1:8000/doc/openapi.json'
Jawabannya { "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 } } } } } ] } } } }
Metode Respon Dokumentasi
Tidak ada fungsional penuh waktu (misalnya, dengan mengimplementasikan antarmuka) yang memungkinkan Anda menambahkan respons metode ke dokumentasi. Tetapi ada peluang, dengan berlangganan acara, untuk menambahkan informasi yang diperlukan sendiri.
Tambahkan pendengar.
# 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' ...
Juga, agar tidak menggambarkan dokumentasi metode secara langsung di pendengar, kami akan membuat antarmuka yang harus diimplementasikan metode itu sendiri.
Sekarang tambahkan metode baru yang akan berisi informasi yang diperlukan.
Jangan lupa untuk mendaftar layanan baru.
services: ... App\Method\UserMethod: public: false tags: [{ method: 'user', name: 'json_rpc_http_server.jsonrpc_method' }] ...
Sekarang, dengan membuat permintaan baru ke /doc/openapi.json , kami mendapatkan data baru.
curl 'http://127.0.0.1:8000/doc/openapi.json'
Jawabannya { "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" } }
Visualisasi dokumentasi JSON
JSON memang keren, tetapi orang biasanya ingin melihat hasil yang lebih manusiawi. File /doc/openapi.json dapat diberikan ke layanan visualisasi eksternal, seperti Swagger Editor .

Jika diinginkan, Anda dapat menginstal UI Swagger di proyek kami. Kami akan menggunakan paket harmbandstra / swagger-ui-bundle .
Untuk publikasi sumber daya yang benar, kami menambahkan yang berikut dengan 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" ] },
Setelah kami meletakkan paket.
$ composer require harmbandstra/swagger-ui-bundle
Hubungkan bundel.
# 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"
Sekarang, mengikuti tautan http://127.0.0.1:8000/docs/, kami mendapatkan dokumentasi dalam bentuk yang indah.

Ringkasan
Sebagai hasil dari semua manipulasi kami mendapat JSON RPC yang berfungsi berdasarkan Symfony 4 dan dokumentasi otomatis OpenAPI dengan visualisasi menggunakan Swagger UI.
Terima kasih semuanya.