
مرحبًا بالجميع ، سنتحدث اليوم عن كيفية تكوين صداقات Symfony 4 و JSON RPC و OpenAPI 3.
هذه المقالة ليست مخصصة للمبتدئين ، يجب أن تفهم بالفعل كيفية التعامل مع Symfony و Depedency Injection وغيرها من الأشياء "المخيفة".
اليوم ، دعنا ننظر إلى تطبيق واحد محدد لـ JSON RPC.
تطبيق
هناك العديد من تطبيقات JSON RPC لـ Symfony ، على وجه الخصوص:
سنتحدث عن الأخير في هذا المقال. هذه المكتبة لديها العديد من المزايا التي حددت خياري.
تم تطويره دون ربط بأي إطار عمل ( yoanm / php-jsonrpc-server-sdk ) ، هناك حزمة لـ Symfony ، وله العديد من الحزم الإضافية التي تتيح لك إضافة فحص المدخلات والوثائق التلقائية والأحداث والواجهات لتكون قادرة على استكمال العمل دون إعادة تعريف.
تركيب
للبدء ، قم بتثبيت symfony / skeleton.
$ composer create-project symfony/skeleton jsonrpc
انتقل إلى مجلد المشروع.
$ cd jsonrpc
وتثبيت المكتبة اللازمة.
$ composer require yoanm/symfony-jsonrpc-http-server
تخصيص.
# config/routes.yaml json-rpc-endpoint: resource: '@JsonRpcHttpServerBundle/Resources/config/routing/endpoint.xml'
# config/packages/json_rpc.yaml json_rpc_http_server: ~
إضافة خدمة من شأنها تخزين جميع أساليبنا.
وأضف الخدمة إلى services.yaml.
# config/services.yaml services: ... mapping_aware_service: class: App\MappingCollector tags: ['json_rpc_http_server.method_aware'] ...
طريقة التنفيذ
تتم إضافة أساليب RPC JSON كخدمات عادية في ملف services.yaml. ننفذ أولاً طريقة ping نفسها.
وأضف كخدمة.
# 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" } ]
الآن نطبق الطريقة التي تستقبل المعلمات. سنعود بيانات الإدخال كإجابة.
# 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
ربط الحزمة.
يجب أن تقوم الطرق التي تحتاج إلى التحقق من الإدخال بتطبيق الواجهة Yoanm \ JsonRpcParamsSymfonyValidator \ Domain \ MethodWithValidatedParamsInterface . دعونا تعديل فئة ParamsMethod قليلا .
الآن إذا قمنا بتنفيذ الطلب مع معلمات فارغة أو مع أخطاء ، سوف نتلقى الأخطاء المقابلة في الرد.
$ 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/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-restrictions-doc .
$ composer require yoanm/symfony-jsonrpc-params-sf-constraints-doc
الآن إذا قدمنا طلبًا ، فسنحصل على أساليب 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" } }
أوبنابي 3
لكي تكون وثائق JSON متوافقة مع معيار OpenAPI 3 ، تحتاج إلى تثبيت yoanm / symfony-jsonrpc-http-server-openapi-doc .
$ composer require yoanm/symfony-jsonrpc-http-server-openapi-doc
تخصيص.
بعد تقديم طلب جديد ، سنتلقى وثائق 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' ...
أيضًا ، حتى لا يتم وصف وثائق الطرق مباشرةً في المستمع ، سنقوم بإنشاء واجهة يجب أن تطبقها الطرق نفسها.
أضف الآن طريقة جديدة تحتوي على المعلومات اللازمة.
لا تنس تسجيل خدمة جديدة.
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- package.
للنشر الصحيح للموارد ، نضيف ما يلي مع 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/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.
شكرا لكم جميعا