├── .gitignore ├── Resources ├── config │ ├── routing.yml │ └── services.yml ├── meta │ └── LICENSE └── doc │ └── index.rst ├── Wa72JsonRpcBundle.php ├── Tests ├── Fixtures │ ├── app │ │ ├── config │ │ │ ├── config2.yml │ │ │ └── config1.yml │ │ ├── Wa72JsonRpcBundleTestKernel1.php │ │ └── Wa72JsonRpcBundleTestKernel2.php │ └── Testparameter.php ├── bootstrap.php ├── Testservice.php ├── JsonRpcController2Test.php └── JsonRpcController1Test.php ├── phpunit.xml.dist ├── README.md ├── LICENSE ├── DependencyInjection ├── Wa72JsonRpcExtension.php ├── Compiler │ └── JsonRpcExposablePass.php └── Configuration.php ├── composer.json ├── .github └── workflows │ └── tests.yml ├── CHANGELOG.md └── Controller └── JsonRpcController.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | composer.phar 4 | -------------------------------------------------------------------------------- /Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | wa72_json_rpc: 2 | path: / 3 | defaults: { _controller: 'wa72_jsonrpc.jsonrpccontroller::execute', optionalTrailingSlash : "/" } 4 | requirements: { optionalTrailingSlash : "[/]{0,1}" } 5 | -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | wa72_jsonrpc.jsonrpccontroller.class: Wa72\JsonRpcBundle\Controller\JsonRpcController 3 | 4 | services: 5 | wa72_jsonrpc.jsonrpccontroller: 6 | public: true 7 | class: '%wa72_jsonrpc.jsonrpccontroller.class%' 8 | arguments: ['@service_container', '%wa72.jsonrpc%'] 9 | 10 | -------------------------------------------------------------------------------- /Wa72JsonRpcBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new JsonRpcExposablePass()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/config/config2.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: dummy 3 | serializer: 4 | enabled: true 5 | property_access: 6 | enabled: true 7 | 8 | wa72_json_rpc: 9 | functions: 10 | testhello: 11 | service: wa72_jsonrpc.testservice 12 | method: hello 13 | 14 | services: 15 | wa72_jsonrpc.testservice: 16 | class: 'Wa72\JsonRpcBundle\Tests\Testservice' 17 | public: true 18 | tags: 19 | - {name: wa72_jsonrpc.exposable} 20 | 21 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./ 6 | 7 | 8 | ./Resources 9 | ./Tests 10 | ./vendor 11 | 12 | 13 | 14 | 15 | ./Tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JsonRpcBundle 2 | ============= 3 | 4 | ![tests](https://github.com/wasinger/jsonrpc-bundle/actions/workflows/tests.yml/badge.svg?branch=master) 5 | [![Latest Version](http://img.shields.io/packagist/v/wa72/jsonrpc-bundle.svg)](https://packagist.org/packages/wa72/jsonrpc-bundle) 6 | [![Downloads from Packagist](http://img.shields.io/packagist/dt/wa72/jsonrpc-bundle.svg)](https://packagist.org/packages/wa72/jsonrpc-bundle) 7 | 8 | 9 | JsonRpcBundle is a bundle for Symfony that allows to easily build a JSON-RPC server for web services using 10 | [JSON-RPC 2.0](http://www.jsonrpc.org/specification). 11 | 12 | The bundle contains a controller that is able to expose methods of any public service registered in the Symfony service 13 | container as a JSON-RPC web service. 14 | 15 | Documentation 16 | ------------- 17 | 18 | Documentation is found in [Resources/doc/index.rst](Resources/doc/index.rst). 19 | 20 | 21 | © 2013-2023 Christoph Singer. Licensed under the MIT license. 22 | -------------------------------------------------------------------------------- /Tests/Fixtures/Testparameter.php: -------------------------------------------------------------------------------- 1 | a = $a; 27 | } 28 | 29 | 30 | public function getA(): string 31 | { 32 | return $this->a; 33 | } 34 | 35 | 36 | public function getB(): string 37 | { 38 | return $this->b; 39 | } 40 | 41 | 42 | public function setB(string $b) 43 | { 44 | $this->b = $b; 45 | } 46 | 47 | 48 | public function getC(): string 49 | { 50 | return $this->c; 51 | } 52 | 53 | 54 | public function setC(string $c) 55 | { 56 | $this->c = $c; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /Tests/Testservice.php: -------------------------------------------------------------------------------- 1 | getA() . $arg3->getB() . $arg3->getC(); 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Christoph Singer, Web-Agentur 72 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | The software is provided "as is", without warranty of any kind, express or 14 | implied, including but not limited to the warranties of merchantability, 15 | fitness for a particular purpose and noninfringement. In no event shall the 16 | authors or copyright holders be liable for any claim, damages or other 17 | liability, whether in an action of contract, tort or otherwise, arising from, 18 | out of or in connection with the software or the use or other dealings in 19 | the software. 20 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/Wa72JsonRpcBundleTestKernel1.php: -------------------------------------------------------------------------------- 1 | load(__DIR__ . '/config/config1.yml'); 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getCacheDir(): string 29 | { 30 | return sys_get_temp_dir().'/Wa72JsonRpcBundle/cache'; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getLogDir(): string 37 | { 38 | return sys_get_temp_dir().'/Wa72JsonRpcBundle/logs'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DependencyInjection/Wa72JsonRpcExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 26 | 27 | $container->setParameter('wa72.jsonrpc', $config); 28 | 29 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 30 | $loader->load('services.yml'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/Wa72JsonRpcBundleTestKernel2.php: -------------------------------------------------------------------------------- 1 | load(__DIR__ . '/config/config2.yml'); 24 | } 25 | 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getCacheDir(): string 31 | { 32 | return sys_get_temp_dir() . '/Wa72JsonRpcBundle/cache'; 33 | } 34 | 35 | /** 36 | * @return string 37 | */ 38 | public function getLogDir(): string 39 | { 40 | return sys_get_temp_dir() . '/Wa72JsonRpcBundle/logs'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"wa72/jsonrpc-bundle", 3 | "description":"JSON-RPC server for Symfony: exposes services registered in the service container as JSON-RPC webservices", 4 | "keywords":["json", "rpc", "jsonrpc", "json-rpc", "webservice", "symfony", "symfony-bundle" ], 5 | "homepage":"https://github.com/wasinger/jsonrpc-bundle", 6 | "license":"MIT", 7 | "type": "symfony-bundle", 8 | "authors":[ 9 | { 10 | "name":"Christoph Singer", 11 | "email":"dev@ccss.de" 12 | } 13 | ], 14 | "require":{ 15 | "php":">=8.0", 16 | "ext-json": "*", 17 | "symfony/framework-bundle":"^5.4|^6|^7", 18 | "symfony/yaml": "^5.4|^6|^7" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "^9", 22 | "jms/serializer-bundle": "^5.4", 23 | "symfony/property-access": "^5.4|^6.0|^7.0", 24 | "symfony/property-info": "^5.4|^6.0|^7.0", 25 | "symfony/serializer": "^5.4|^6.0|^7.0" 26 | }, 27 | "suggest": { 28 | "jms/serializer-bundle": "If you want to use JMS Serializer for serialization/deserialization", 29 | "symfony/serializer": "For using Symfony's Serializer component" 30 | }, 31 | "autoload":{ 32 | "psr-4":{ 33 | "Wa72\\JsonRpcBundle\\":"" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/JsonRpcExposablePass.php: -------------------------------------------------------------------------------- 1 | getDefinition('wa72_jsonrpc.jsonrpccontroller'); 20 | $services = $container->findTaggedServiceIds('wa72_jsonrpc.exposable'); 21 | foreach ($services as $service => $attributes) { 22 | $definition->addMethodCall('addService', [$service]); 23 | } 24 | 25 | // Add an alias for the serializer service to make it public 26 | try { 27 | $serializer = $container->getDefinition('serializer'); 28 | } catch (\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException $e) { 29 | $serializer = null; 30 | } 31 | if ($serializer) { 32 | $a = new Alias('serializer', true); 33 | $container->setAlias('wa72_jsonrpc.serializer', $a); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | php: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | php: [8.0, 8.1, 8.2, 8.3, 8.4] 19 | dependency-version: [prefer-lowest, prefer-stable] 20 | optional-deps: [without-jms, without-serializer, with-both] 21 | name: PHP ${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.optional-deps }} 22 | 23 | steps: 24 | - name: checkout code 25 | uses: actions/checkout@v4 26 | 27 | - name: setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | coverage: xdebug 32 | 33 | - name: get composer cache directory 34 | id: composer-cache 35 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 36 | 37 | - name: cache composer dependencies 38 | uses: actions/cache@v4 39 | with: 40 | path: ${{ steps.composer-cache.outputs.dir }} 41 | key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ matrix.dependency-version }}-${{ hashFiles('**/composer.lock') }} 42 | restore-keys: | 43 | ${{ runner.os }}-composer-${{ matrix.php }}-${{ matrix.dependency-version }}- 44 | ${{ runner.os }}-composer-${{ matrix.php }}- 45 | ${{ runner.os }}-composer- 46 | 47 | - name: install dependencies 48 | run: composer update --${{ matrix.dependency-version }} --no-progress --no-interaction 49 | 50 | - name: remove optional dependency (jms/serializer-bundle) 51 | if: matrix.optional-deps == 'without-jms' 52 | run: composer remove jms/serializer-bundle --dev --no-progress --no-interaction 53 | 54 | - name: remove optional dependency (symfony/serializer) 55 | if: matrix.optional-deps == 'without-serializer' 56 | run: composer remove symfony/serializer --dev --no-progress --no-interaction 57 | 58 | - name: run tests 59 | run: php vendor/bin/phpunit 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.8.0 2025-07-26 ### 2 | - Support for Symfony 7, minimum required Symfony version is now 5.4. 3 | - JMS\SerializerBundle is no longer a required dependency, symfony/serializer can be used instead. 4 | - Configuration changes: 5 | - Use `serialization_context` instead of `jms_serialization_context`. 6 | - Use `enable_max_depth' instead of `max_depth_checks`. 7 | 8 | ### v0.7.0-beta 2023-10-24 ### 9 | - Updated for Symfony 5 and 6 and PHP 8. 10 | 11 | ### v0.6.1 2018-03-10 ### 12 | - compatibility with Symfony 4, thanks to @shreypuranik 13 | - make unit tests compatible with newer phpunit versions 14 | 15 | ### v0.6.0 2017-02-09 ### 16 | - added compatibility with Symfony 3, thanks to @JasonMiesionczek 17 | - minimum required Symfony version is now 2.7 18 | 19 | ### v0.5.0 2014-12-17 ### 20 | - It is now possible to call methods that require objects as parameters (in previous versions only methods with scalar 21 | and array parameters could be called). That's why JMSSerializerBundle is now a required dependency. 22 | For this to work, the following conditions must be met: 23 | - The method parameters must be correctly type hinted 24 | - The parameter classes must contain [jms_serializer `@Type` annotations for its properties](http://jmsyst.com/libs/serializer/master/reference/annotations#type) 25 | 26 | ### v0.4.4 2014-11-28 ### 27 | - The test service is no longer registered automatically. If you want to use it, 28 | you must define it in the config.yml of your application, see updated [Resources/doc/index.rst](Resources/doc/index.rst). 29 | 30 | ### v0.4.2 2014-11-28 ### 31 | - fixed bug introduced in v0.2.0: named parameters given in associative arrays were always passed to the method in 32 | the order they were given, not by their names. Thanks to @teet. 33 | 34 | ### v0.4.1 2014-11-19 ### 35 | - raised required symfony version to 2.3 36 | - Added mandatory LICENSE and index.rst files 37 | - only metadata, no code changes 38 | 39 | ### v0.4.0 2014-10-23 ### 40 | - All public methods of services tagged with 'wa72_jsonrpc.exposable' can be called via JSON-RPC. The method name 41 | to be used in the RPC call is "service:method", i.e. the name of the service and the method separated by colon. -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 17 | 18 | $rootNode 19 | ->children() 20 | ->arrayNode('functions') 21 | ->useAttributeAsKey('function') 22 | ->prototype('array') 23 | ->children() 24 | ->scalarNode('service')->end() 25 | ->scalarNode('method')->end() 26 | ->arrayNode('serialization_context') 27 | ->children() 28 | ->arrayNode('groups') 29 | ->beforeNormalization() 30 | ->ifTrue(function ($v) { return is_string($v); }) 31 | ->then(function ($v) { return array($v); }) 32 | ->end() 33 | ->prototype('scalar')->end() 34 | ->end() 35 | ->scalarNode('version')->end() 36 | ->booleanNode('max_depth_checks')->setDeprecated( 37 | 'wa72/json-rpc-bundle', '0.8.0', 38 | 'The "%node%" option is deprecated. Use "enable_max_depth" instead.' 39 | )->end() 40 | ->booleanNode('enable_max_depth')->end() 41 | ->end() 42 | ->end() 43 | ->arrayNode('jms_serialization_context') 44 | ->setDeprecated( 45 | 'wa72/json-rpc-bundle', '0.8.0', 46 | 'The "%node%" option is deprecated. Use "serialization_context" instead.' 47 | ) 48 | ->children() 49 | ->arrayNode('groups') 50 | ->beforeNormalization() 51 | ->ifTrue(function ($v) { return is_string($v); }) 52 | ->then(function ($v) { return array($v); }) 53 | ->end() 54 | ->prototype('scalar')->end() 55 | ->end() 56 | ->scalarNode('version')->end() 57 | ->booleanNode('max_depth_checks')->end() 58 | ->end() 59 | ->end() 60 | ->end() 61 | ->end() 62 | ->end() 63 | ->end(); 64 | 65 | return $treeBuilder; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Resources/doc/index.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | JsonRpcBundle 3 | ============= 4 | 5 | JsonRpcBundle is a bundle for Symfony 2.7 and up that allows to easily build a JSON-RPC server for web services using `JSON-RPC 2.0`_. 6 | 7 | The bundle contains a controller that is able to expose methods of any public service registered in the Symfony service container as a JSON-RPC web service. The return value of the service method is converted to JSON using `jms\_serializer`_. 8 | 9 | Of course, it doesn't simply expose all your services' methods to the public, but only those explicitely mentioned in the configuration. And service methods cannot be called by it's original name but by an alias to be defined in the configuration. 10 | 11 | 12 | Installation 13 | ============ 14 | 15 | 1. Add "wa72/jsonrpc-bundle" as requirement to your composer.json 16 | 17 | 2. Add "new Wa72\\JsonRpcBundle\\Wa72JsonRpcBundle()" in your AppKernel::registerBundles() function 18 | 19 | 3. Import the bundle's route in your routing.yml: 20 | 21 | .. code-block:: yaml 22 | 23 | # app/config/routing.yml 24 | wa72_json_rpc: 25 | resource: "@Wa72JsonRpcBundle/Resources/config/routing.yml" 26 | prefix: /jsonrpc 27 | 28 | Your JSON-RPC web service will then be available in your project calling the /jsonrpc/ URL. 29 | 30 | Configuration 31 | ============= 32 | 33 | You must configure which functions of the services registered in the Service Container will be available as web services. 34 | There are two methods how to do this: 35 | 36 | Method 1: Expose single methods from any service via yaml configuration 37 | ----------------------------------------------------------------------- 38 | 39 | Configuration is done under the "wa72\_json\_rpc" key of your configuration (usually defined in your app/config/config.yml). 40 | To enable a Symfony2 service method to be called as a JSON-RPC web service, add it to the "functions" array of the configuration. 41 | The key of an entry of the "functions" array is the alias name for the method to be called over RPC and it needs two sub keys: 42 | "service" specifies the name of the service and "method" the name of the method to call. Example: 43 | 44 | .. code-block:: yaml 45 | 46 | # app/config/config.yml 47 | wa72_json_rpc: 48 | functions: 49 | myfunction1: 50 | service: "mybundle.servicename" 51 | method: "methodofservice" 52 | anotherfunction: 53 | service: "bundlename.foo" 54 | method: "bar" 55 | 56 | In this example, "myfunction1" and "anotherfunction" are aliases for service methods that are used as JSON-RPC method names. 57 | A method name "myfunction1" in the JSON-RPC call will then call the method "methodofservice" of service "mybundle.servicename". 58 | 59 | If you use `jms\_serializer`_ you can also configure exclusion strategies (groups, version, or max depth checks) : 60 | 61 | .. code-block:: yaml 62 | 63 | # app/config/config.yml 64 | wa72_json_rpc: 65 | functions: 66 | myfunction1: 67 | service: "mybundle.servicename" 68 | method: "methodofservice" 69 | jms_serialization_context: 70 | group: "my_group" 71 | version: "1" 72 | max_depth_checks: true 73 | 74 | 75 | Method 2: tag a sercive to make all it's public methods callable by JSON-RPC 76 | ---------------------------------------------------------------------------- 77 | 78 | Starting with v0.4.0, it is also possible to fully expose all methods of a service by tagging it with ``wa72\_jsonrpc.exposable``. 79 | All public methods of services tagged with 'wa72\_jsonrpc.exposable' can be called via JSON-RPC. The method name 80 | to be used in the RPC call is "service\:method", i.e. the name of the service and the method separated by colon. 81 | 82 | 83 | Notes 84 | ===== 85 | 86 | **New in Version 0.5.0**: It is now possible to call methods that require objects as parameters (in previous versions 87 | only methods with scalar and array parameters could be called). 88 | That's why *JMSSerializerBundle is now a required dependency*. 89 | For this to work, the following conditions must be met: 90 | 91 | - The method parameters must be correctly type hinted 92 | - The classes used as parameters must contain `jms\_serializer @Type annotations for their properties `_ 93 | 94 | 95 | Testing 96 | ======= 97 | 98 | The bundle comes with a simple test service. If you have imported the bundle's routing to ``/jsonrpc`` (see above) 99 | register the test service in your DI container: 100 | 101 | .. code-block:: yaml 102 | 103 | # app/config/config_dev.yml 104 | services: 105 | wa72_jsonrpc.testservice: 106 | class: Wa72\JsonRpcBundle\Tests\Testservice 107 | tags: 108 | - {name: wa72_jsonrpc.exposable} 109 | 110 | You should then be able to test your service by sending a JSON-RPC request using curl: 111 | 112 | .. code-block:: bash 113 | 114 | curl -XPOST http://your-symfony-project/app_dev.php/jsonrpc/ -d '{"jsonrpc":"2.0","method":"wa72_jsonrpc.testservice:hello","id":"foo","params":{"name":"Joe"}}' 115 | 116 | and you should get the following answer: 117 | 118 | .. code-block:: json 119 | 120 | {"jsonrpc":"2.0","result":"Hello Joe!","id":"foo"} 121 | 122 | There are also unit tests for phpunit. Just install the required development dependencies using ``composer install`` and run 123 | ``vendor/bin/phpunit`` in the root directory of the project. 124 | 125 | © 2013-2018 Christoph Singer, Web-Agentur 72. Licensed under the MIT license. 126 | 127 | 128 | .. _`JSON-RPC 2.0`: http://www.jsonrpc.org/specification 129 | .. _`jms\_serializer`: https://github.com/schmittjoh/JMSSerializerBundle -------------------------------------------------------------------------------- /Tests/JsonRpcController2Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Symfony Serializer not available.'); 27 | } else { 28 | $this->fail('No serializer available. Please install JMS Serializer Bundle or Symfony Serializer.'); 29 | } 30 | } else { 31 | $this->kernel = new \Wa72JsonRpcBundleTestKernel2('test', true); 32 | $this->kernel->boot(); 33 | $container = $this->kernel->getContainer(); 34 | $this->serializer = $container->get('wa72_jsonrpc.serializer'); 35 | $this->controller = $container->get('wa72_jsonrpc.jsonrpccontroller'); 36 | } 37 | } 38 | 39 | public function testHello() 40 | { 41 | $requestdata = array( 42 | 'jsonrpc' => '2.0', 43 | 'id' => 'test', 44 | 'method' => 'testhello', 45 | 'params' => array('name' => 'Joe') 46 | ); 47 | $response = $this->makeRequest($this->controller, $requestdata); 48 | $this->assertEquals('2.0', $response['jsonrpc']); 49 | $this->assertEquals('test', $response['id']); 50 | $this->assertArrayHasKey('result', $response); 51 | $this->assertArrayNotHasKey('error', $response); 52 | $this->assertEquals('Hello Joe!', $response['result']); 53 | 54 | // Test: missing parameter 55 | $requestdata = array( 56 | 'jsonrpc' => '2.0', 57 | 'id' => 'test', 58 | 'method' => 'testhello' 59 | ); 60 | $response = $this->makeRequest($this->controller, $requestdata); 61 | $this->assertArrayHasKey('error', $response); 62 | $this->assertArrayNotHasKey('result', $response); 63 | $this->assertEquals(-32602, $response['error']['code']); 64 | } 65 | 66 | public function testService() 67 | { 68 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 69 | $requestdata = array( 70 | 'jsonrpc' => '2.0', 71 | 'id' => 'testservice', 72 | 'method' => 'wa72_jsonrpc.testservice:hello', 73 | 'params' => array('name' => 'Max') 74 | ); 75 | 76 | $response = $this->makeRequest($controller, $requestdata); 77 | $this->assertEquals('2.0', $response['jsonrpc']); 78 | $this->assertEquals('testservice', $response['id']); 79 | $this->assertArrayNotHasKey('error', $response); 80 | $this->assertArrayHasKey('result', $response); 81 | $this->assertEquals('Hello Max!', $response['result']); 82 | 83 | // Test: non-existing service should return "Method not found" error 84 | $requestdata = array( 85 | 'jsonrpc' => '2.0', 86 | 'id' => 'testservice', 87 | 'method' => 'someservice:somemethod', 88 | 'params' => array('name' => 'Max') 89 | ); 90 | 91 | $response = $this->makeRequest($controller, $requestdata); 92 | $this->assertEquals('2.0', $response['jsonrpc']); 93 | $this->assertEquals('testservice', $response['id']); 94 | $this->assertArrayNotHasKey('result', $response); 95 | $this->assertArrayHasKey('error', $response); 96 | $this->assertEquals(-32601, $response['error']['code']); 97 | } 98 | 99 | public function testParameters() 100 | { 101 | // params as associative array in right order 102 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 103 | $requestdata = array( 104 | 'jsonrpc' => '2.0', 105 | 'id' => 'parametertest', 106 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 107 | 'params' => array('arg1' => 'abc', 'arg2' => 'def', 'arg_array' => array()) 108 | ); 109 | 110 | $response = $this->makeRequest($controller, $requestdata); 111 | $this->assertArrayNotHasKey('error', $response); 112 | $this->assertArrayHasKey('result', $response); 113 | $this->assertEquals('abcdef', $response['result']); 114 | 115 | // params as simple array in right order 116 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 117 | $requestdata = array( 118 | 'jsonrpc' => '2.0', 119 | 'id' => 'parametertest', 120 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 121 | 'params' => array('abc', 'def', array()) 122 | ); 123 | 124 | $response = $this->makeRequest($controller, $requestdata); 125 | $this->assertArrayNotHasKey('error', $response); 126 | $this->assertArrayHasKey('result', $response); 127 | $this->assertEquals('abcdef', $response['result']); 128 | 129 | // params as associative array in mixed order 130 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 131 | $requestdata = array( 132 | 'jsonrpc' => '2.0', 133 | 'id' => 'parametertest', 134 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 135 | 'params' => array('arg_array' => array(), 'arg2' => 'def', 'arg1' => 'abc') 136 | ); 137 | 138 | $response = $this->makeRequest($controller, $requestdata); 139 | $this->assertArrayNotHasKey('error', $response); 140 | $this->assertArrayHasKey('result', $response); 141 | $this->assertEquals('abcdef', $response['result']); 142 | 143 | // params with objects 144 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 145 | $arg3 = new Testparameter('abc'); 146 | $arg3->setB('def'); 147 | $arg3->setC('ghi'); 148 | $requestdata = array( 149 | 'jsonrpc' => '2.0', 150 | 'id' => 'testParameterTypes', 151 | 'method' => 'wa72_jsonrpc.testservice:testParameterTypes', 152 | 'params' => array('arg1' => array(), 'arg2' => new \stdClass(), 'arg3' => $arg3) 153 | ); 154 | 155 | $response = $this->makeRequest($controller, $requestdata); 156 | $this->assertArrayNotHasKey('error', $response); 157 | $this->assertArrayHasKey('result', $response); 158 | $this->assertEquals('abcdefghi', $response['result']); 159 | } 160 | 161 | public function testAddMethod() 162 | { 163 | $requestdata = array( 164 | 'jsonrpc' => '2.0', 165 | 'id' => 'test', 166 | 'method' => 'testhi', 167 | 'params' => array('name' => 'Tom') 168 | ); 169 | // this request will fail because there is no such method "testhi" 170 | $response = $this->makeRequest($this->controller, $requestdata); 171 | $this->assertArrayHasKey('error', $response); 172 | $this->assertArrayNotHasKey('result', $response); 173 | $this->assertEquals(-32601, $response['error']['code']); 174 | 175 | // add the method definition for "testhi" 176 | $this->controller->addMethod('testhi', 'wa72_jsonrpc.testservice', 'hi'); 177 | 178 | // now the request should succeed 179 | $response = $this->makeRequest($this->controller, $requestdata); 180 | $this->assertArrayHasKey('result', $response); 181 | $this->assertArrayNotHasKey('error', $response); 182 | $this->assertEquals('Hi Tom!', $response['result']); 183 | } 184 | 185 | private function makeRequest($controller, $requestdata) 186 | { 187 | return json_decode($controller->execute( 188 | new Request([], [], [], [], [], [], $this->serializer->serialize($requestdata, 'json')) 189 | )->getContent(), true); 190 | } 191 | 192 | /** 193 | * Shuts the kernel down if it was used in the test. 194 | */ 195 | protected function tearDown(): void 196 | { 197 | $this->kernel->shutdown(); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /Tests/JsonRpcController1Test.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('JMS Serializer not available.'); 28 | } else { 29 | $this->fail('No serializer available. Please install JMS Serializer Bundle or Symfony Serializer.'); 30 | } 31 | } else { 32 | $this->kernel = new \Wa72JsonRpcBundleTestKernel1('test', false); 33 | $this->kernel->boot(); 34 | $container = $this->kernel->getContainer(); 35 | $this->serializer = $container->get('jms_serializer'); 36 | $this->controller = $container->get('wa72_jsonrpc.jsonrpccontroller'); 37 | } 38 | } 39 | 40 | public function testHello() 41 | { 42 | $requestdata = array( 43 | 'jsonrpc' => '2.0', 44 | 'id' => 'test', 45 | 'method' => 'testhello', 46 | 'params' => array('name' => 'Joe') 47 | ); 48 | $response = $this->makeRequest($this->controller, $requestdata); 49 | $this->assertEquals('2.0', $response['jsonrpc']); 50 | $this->assertEquals('test', $response['id']); 51 | $this->assertArrayHasKey('result', $response); 52 | $this->assertArrayNotHasKey('error', $response); 53 | $this->assertEquals('Hello Joe!', $response['result']); 54 | 55 | // Test: missing parameter 56 | $requestdata = array( 57 | 'jsonrpc' => '2.0', 58 | 'id' => 'test', 59 | 'method' => 'testhello' 60 | ); 61 | $response = $this->makeRequest($this->controller, $requestdata); 62 | $this->assertArrayHasKey('error', $response); 63 | $this->assertArrayNotHasKey('result', $response); 64 | $this->assertEquals(-32602, $response['error']['code']); 65 | } 66 | 67 | public function testService() 68 | { 69 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 70 | $requestdata = array( 71 | 'jsonrpc' => '2.0', 72 | 'id' => 'testservice', 73 | 'method' => 'wa72_jsonrpc.testservice:hello', 74 | 'params' => array('name' => 'Max') 75 | ); 76 | 77 | $response = $this->makeRequest($controller, $requestdata); 78 | $this->assertEquals('2.0', $response['jsonrpc']); 79 | $this->assertEquals('testservice', $response['id']); 80 | $this->assertArrayNotHasKey('error', $response); 81 | $this->assertArrayHasKey('result', $response); 82 | $this->assertEquals('Hello Max!', $response['result']); 83 | 84 | // Test: non-existing service should return "Method not found" error 85 | $requestdata = array( 86 | 'jsonrpc' => '2.0', 87 | 'id' => 'testservice', 88 | 'method' => 'someservice:somemethod', 89 | 'params' => array('name' => 'Max') 90 | ); 91 | 92 | $response = $this->makeRequest($controller, $requestdata); 93 | $this->assertEquals('2.0', $response['jsonrpc']); 94 | $this->assertEquals('testservice', $response['id']); 95 | $this->assertArrayNotHasKey('result', $response); 96 | $this->assertArrayHasKey('error', $response); 97 | $this->assertEquals(-32601, $response['error']['code']); 98 | } 99 | 100 | public function testParameters() 101 | { 102 | // params as associative array in right order 103 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 104 | $requestdata = array( 105 | 'jsonrpc' => '2.0', 106 | 'id' => 'parametertest', 107 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 108 | 'params' => array('arg1' => 'abc', 'arg2' => 'def', 'arg_array' => array()) 109 | ); 110 | 111 | $response = $this->makeRequest($controller, $requestdata); 112 | $this->assertArrayNotHasKey('error', $response); 113 | $this->assertArrayHasKey('result', $response); 114 | $this->assertEquals('abcdef', $response['result']); 115 | 116 | // params as simple array in right order 117 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 118 | $requestdata = array( 119 | 'jsonrpc' => '2.0', 120 | 'id' => 'parametertest', 121 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 122 | 'params' => array('abc', 'def', array()) 123 | ); 124 | 125 | $response = $this->makeRequest($controller, $requestdata); 126 | $this->assertArrayNotHasKey('error', $response); 127 | $this->assertArrayHasKey('result', $response); 128 | $this->assertEquals('abcdef', $response['result']); 129 | 130 | // params as associative array in mixed order 131 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 132 | $requestdata = array( 133 | 'jsonrpc' => '2.0', 134 | 'id' => 'parametertest', 135 | 'method' => 'wa72_jsonrpc.testservice:parametertest', 136 | 'params' => array('arg_array' => array(), 'arg2' => 'def', 'arg1' => 'abc') 137 | ); 138 | 139 | $response = $this->makeRequest($controller, $requestdata); 140 | $this->assertArrayNotHasKey('error', $response); 141 | $this->assertArrayHasKey('result', $response); 142 | $this->assertEquals('abcdef', $response['result']); 143 | 144 | // params with objects 145 | $controller = $this->kernel->getContainer()->get('wa72_jsonrpc.jsonrpccontroller'); 146 | $arg3 = new Testparameter('abc'); 147 | $arg3->setB('def'); 148 | $arg3->setC('ghi'); 149 | $requestdata = array( 150 | 'jsonrpc' => '2.0', 151 | 'id' => 'testParameterTypes', 152 | 'method' => 'wa72_jsonrpc.testservice:testParameterTypes', 153 | 'params' => array('arg1' => array(), 'arg2' => new \stdClass(), 'arg3' => $arg3) 154 | ); 155 | 156 | $response = $this->makeRequest($controller, $requestdata); 157 | $this->assertArrayNotHasKey('error', $response); 158 | $this->assertArrayHasKey('result', $response); 159 | $this->assertEquals('abcdefghi', $response['result']); 160 | } 161 | 162 | public function testAddMethod() 163 | { 164 | $requestdata = array( 165 | 'jsonrpc' => '2.0', 166 | 'id' => 'test', 167 | 'method' => 'testhi', 168 | 'params' => array('name' => 'Tom') 169 | ); 170 | // this request will fail because there is no such method "testhi" 171 | $response = $this->makeRequest($this->controller, $requestdata); 172 | $this->assertArrayHasKey('error', $response); 173 | $this->assertArrayNotHasKey('result', $response); 174 | $this->assertEquals(-32601, $response['error']['code']); 175 | 176 | // add the method definition for "testhi" 177 | $this->controller->addMethod('testhi', 'wa72_jsonrpc.testservice', 'hi'); 178 | 179 | // now the request should succeed 180 | $response = $this->makeRequest($this->controller, $requestdata); 181 | $this->assertArrayHasKey('result', $response); 182 | $this->assertArrayNotHasKey('error', $response); 183 | $this->assertEquals('Hi Tom!', $response['result']); 184 | } 185 | 186 | private function makeRequest($controller, $requestdata) 187 | { 188 | $container = $this->kernel->getContainer(); 189 | if ($container->has('jms_serializer')) { 190 | $serializer = $container->get('jms_serializer'); 191 | } elseif ($container->has('serializer')) { 192 | $serializer = $container->get('serializer'); 193 | } else { 194 | $this->fail('No serializer service found in container.'); 195 | } 196 | return json_decode($controller->execute( 197 | new Request([], [], [], [], [], [], $serializer->serialize($requestdata, 'json')) 198 | )->getContent(), true); 199 | } 200 | 201 | /** 202 | * Shuts the kernel down if it was used in the test. 203 | */ 204 | protected function tearDown(): void 205 | { 206 | $this->kernel->shutdown(); 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /Controller/JsonRpcController.php: -------------------------------------------------------------------------------- 1 | array( 25 | * 'myfunction1' => array( 26 | * 'service' => 'mybundle.servicename', 27 | * 'method' => 'methodofservice' 28 | * ), 29 | * 'anotherfunction' => array( 30 | * 'service' => 'mybundle.foo', 31 | * 'method' => 'bar' 32 | * ) 33 | * ) 34 | * ); 35 | * 36 | * A method name "myfunction1" in the RPC request will then call 37 | * $this->container->get('mybundle.servicename')->methodofservice() 38 | * 39 | * If you want to add a service completely so that all public methods of 40 | * this service may be called, use the addService($servicename) method. 41 | * Methods of the services added this way can be called remotely using 42 | * "servicename:method" as RPC method name. 43 | * 44 | * @license MIT 45 | * @author Christoph Singer 46 | * 47 | */ 48 | class JsonRpcController 49 | { 50 | private ContainerInterface $container; 51 | 52 | const PARSE_ERROR = -32700; 53 | const INVALID_REQUEST = -32600; 54 | const METHOD_NOT_FOUND = -32601; 55 | const INVALID_PARAMS = -32602; 56 | const INTERNAL_ERROR = -32603; 57 | 58 | /** 59 | * Functions that are allowed to be called 60 | */ 61 | private array $functions = []; 62 | 63 | /** 64 | * Array of names of fully exposed services (all methods of this services are allowed to be called) 65 | */ 66 | private array $services = []; 67 | 68 | 69 | private JMS_SerializerInterface|SerializerInterface $serializer; 70 | 71 | 72 | private JMS_SerializationContext|array $serializationContext = []; 73 | 74 | /** 75 | * @param ContainerInterface $container 76 | * @param array $config Associative array for configuration, expects at least a key "functions" 77 | * @throws \InvalidArgumentException 78 | */ 79 | public function __construct(ContainerInterface $container, array $config) 80 | { 81 | if (isset($config['functions'])) { 82 | if (!is_array($config['functions'])) throw new \InvalidArgumentException('Configuration parameter "functions" must be array'); 83 | $this->functions = $config['functions']; 84 | } 85 | $this->container = $container; 86 | if ($this->container->has('jms_serializer')) { 87 | $this->serializer = $this->container->get('jms_serializer'); 88 | } elseif ($this->container->has('wa72_jsonrpc.serializer')) { 89 | $this->serializer = $this->container->get('wa72_jsonrpc.serializer'); 90 | } else { 91 | throw new \InvalidArgumentException('No serializer service found in container. Please install jms/serializer-bundle or symfony/serializer.'); 92 | } 93 | } 94 | 95 | /** 96 | * @param Request $httprequest 97 | * @return Response 98 | */ 99 | public function execute(Request $httprequest): Response 100 | { 101 | $json = $httprequest->getContent(); 102 | $request = json_decode($json, true); 103 | $requestId = ($request['id'] ?? null); 104 | 105 | if ($request === null) { 106 | return $this->getErrorResponse(self::PARSE_ERROR, null); 107 | } elseif (!(isset($request['jsonrpc']) && isset($request['method']) && $request['jsonrpc'] == '2.0')) { 108 | return $this->getErrorResponse(self::INVALID_REQUEST, $requestId); 109 | } 110 | 111 | if (in_array($request['method'], array_keys($this->functions))) { 112 | $servicename = $this->functions[$request['method']]['service']; 113 | $method = $this->functions[$request['method']]['method']; 114 | } else { 115 | if (count($this->services) && strpos($request['method'], ':') > 0) { 116 | list($servicename, $method) = explode(':', $request['method']); 117 | if (!in_array($servicename, $this->services)) { 118 | return $this->getErrorResponse(self::METHOD_NOT_FOUND, $requestId); 119 | } 120 | } else { 121 | return $this->getErrorResponse(self::METHOD_NOT_FOUND, $requestId); 122 | } 123 | } 124 | try { 125 | $service = $this->container->get($servicename); 126 | } catch (ServiceNotFoundException $e) { 127 | return $this->getErrorResponse(self::METHOD_NOT_FOUND, $requestId); 128 | } 129 | $params = ($request['params'] ?? []); 130 | 131 | if (is_callable([$service, $method])) { 132 | $r = new \ReflectionMethod($service, $method); 133 | $rps = $r->getParameters(); 134 | 135 | if (is_array($params)) { 136 | if (!(count($params) >= $r->getNumberOfRequiredParameters() 137 | && count($params) <= $r->getNumberOfParameters()) 138 | ) { 139 | return $this->getErrorResponse(self::INVALID_PARAMS, $requestId, 140 | sprintf('Number of given parameters (%d) does not match the number of expected parameters (%d required, %d total)', 141 | count($params), $r->getNumberOfRequiredParameters(), $r->getNumberOfParameters())); 142 | } 143 | 144 | } 145 | if ($this->isAssoc($params)) { 146 | $newparams = []; 147 | foreach ($rps as $i => $rp) { 148 | $name = $rp->name; 149 | if (!isset($params[$rp->name]) && !$rp->isOptional()) { 150 | return $this->getErrorResponse(self::INVALID_PARAMS, $requestId, 151 | sprintf('Parameter %s is missing', $name)); 152 | } 153 | if (isset($params[$rp->name])) { 154 | $newparams[] = $params[$rp->name]; 155 | } else { 156 | $newparams[] = null; 157 | } 158 | } 159 | $params = $newparams; 160 | } 161 | 162 | // correctly deserialize object parameters 163 | foreach ($params as $index => $param) { 164 | // if the json_decode'd param value is an array but an object is expected as method parameter, 165 | // re-encode the array value to json and correctly decode it using the serializer. 166 | // 167 | // TODO: since PHP 8, the method type hints can include union types, so we need to handle those as well. 168 | if (is_array($param) && !$rps[$index]->isArray() && $rps[$index]->getClass() != null) { 169 | $class = $rps[$index]->getClass()->getName(); 170 | $params[$index] = $this->deserialize(json_encode($param), $class); 171 | } 172 | } 173 | 174 | try { 175 | $result = call_user_func_array([$service, $method], $params); 176 | } catch (\Exception $e) { 177 | return $this->getErrorResponse(self::INTERNAL_ERROR, $requestId, $this->convertExceptionToErrorData($e)); 178 | } 179 | $response = ['jsonrpc' => '2.0']; 180 | $response['result'] = $result; 181 | $response['id'] = $requestId; 182 | $response = $this->serialize($response, $request['method']); 183 | return JsonResponse::fromJsonString($response); 184 | } else { 185 | return $this->getErrorResponse(self::METHOD_NOT_FOUND, $requestId); 186 | } 187 | } 188 | 189 | /** 190 | * Add a new function that can be called by RPC 191 | * 192 | * @param string $alias The function name used in the RPC call 193 | * @param string $service The service name of the method to call 194 | * @param string $method The method of $service 195 | * @param bool $overwrite Whether to overwrite an existing function 196 | * @throws \InvalidArgumentException 197 | */ 198 | public function addMethod($alias, $service, $method, $overwrite = false) 199 | { 200 | if (isset($this->functions[$alias]) && !$overwrite) { 201 | throw new \InvalidArgumentException('JsonRpcController: The function "' . $alias . '" already exists.'); 202 | } 203 | $this->functions[$alias] = [ 204 | 'service' => $service, 205 | 'method' => $method 206 | ]; 207 | } 208 | 209 | /** 210 | * Add a new service that is fully exposed by json-rpc 211 | * 212 | * @param string $service The id of a service 213 | */ 214 | public function addService($service) 215 | { 216 | $this->services[] = $service; 217 | } 218 | 219 | /** 220 | * Remove a method definition 221 | * 222 | * @param string $alias 223 | */ 224 | public function removeMethod($alias) 225 | { 226 | if (isset($this->functions[$alias])) { 227 | unset($this->functions[$alias]); 228 | } 229 | } 230 | 231 | protected function convertExceptionToErrorData(\Exception $e): string 232 | { 233 | return $e->getMessage(); 234 | } 235 | 236 | protected function getError($code): array 237 | { 238 | $message = ''; 239 | switch ($code) { 240 | case self::PARSE_ERROR: 241 | $message = 'Parse error'; 242 | break; 243 | case self::INVALID_REQUEST: 244 | $message = 'Invalid request'; 245 | break; 246 | case self::METHOD_NOT_FOUND: 247 | $message = 'Method not found'; 248 | break; 249 | case self::INVALID_PARAMS: 250 | $message = 'Invalid params'; 251 | break; 252 | case self::INTERNAL_ERROR: 253 | $message = 'Internal error'; 254 | break; 255 | } 256 | 257 | return array('code' => $code, 'message' => $message); 258 | } 259 | 260 | protected function getErrorResponse($code, $id, $data = null): JsonResponse 261 | { 262 | $response = array('jsonrpc' => '2.0'); 263 | $response['error'] = $this->getError($code); 264 | 265 | if ($data != null) { 266 | $response['error']['data'] = $data; 267 | } 268 | 269 | $response['id'] = $id; 270 | 271 | return new JsonResponse($response); 272 | } 273 | 274 | /** 275 | * Serialize the return value of a method call to JSON. 276 | */ 277 | protected function serialize(mixed $data, string $rpc_method): string 278 | { 279 | return $this->serializer->serialize($data, 'json', $this->getSerializationContext($rpc_method)); 280 | } 281 | 282 | /** 283 | * Deserialize parameter values coming with the RPC request to the expected type. 284 | */ 285 | protected function deserialize(string $json, string $class): mixed 286 | { 287 | return $this->serializer->deserialize($json, $class, 'json'); 288 | } 289 | 290 | /** 291 | * Set SerializationContext 292 | * 293 | */ 294 | public function setSerializationContext(array|JMS_SerializationContext $context): void 295 | { 296 | if ($this->serializer instanceof JMS_SerializerInterface && !($context instanceof JMS_SerializationContext)) { 297 | throw new \InvalidArgumentException('If jms_serializer is used, the SerializationContext must be an instance of JMS_SerializationContext'); 298 | } 299 | if ($this->serializer instanceof SerializerInterface && !is_array($context)) { 300 | throw new \InvalidArgumentException('If symfony/serializer is used, the SerializationContext must be an array'); 301 | } 302 | $this->serializationContext = $context; 303 | } 304 | 305 | /** 306 | * Get SerializationContext for a given rpc_method. 307 | * 308 | * The context will be created from the configuration array for this method if available, 309 | * otherwise the default serialization context (set by $this->setSerializationContext()) will be used. 310 | */ 311 | protected function getSerializationContext(string $rpc_method): JMS_SerializationContext|array 312 | { 313 | $functionConfig = $this->functions[$rpc_method] ?? []; 314 | if ($this->serializer instanceof JMS_SerializerInterface) { 315 | // legacy support for jms_serialization_context 316 | if (isset($functionConfig['jms_serialization_context'])) { 317 | $functionConfig['serialization_context'] = $functionConfig['jms_serialization_context']; 318 | } 319 | if (isset($functionConfig['serialization_context'])) { 320 | $context = JMS_SerializationContext::create(); 321 | if (isset($functionConfig['serialization_context']['groups'])) { 322 | $context->setGroups($functionConfig['jms_serialization_context']['groups']); 323 | } 324 | if (isset($functionConfig['serialization_context']['version'])) { 325 | $context->setVersion($functionConfig['jms_serialization_context']['version']); 326 | } 327 | if (!empty($functionConfig['serialization_context']['max_depth_checks']) || !empty($functionConfig['serialization_context']['enable_max_depth'])) { 328 | $context->enableMaxDepthChecks(); 329 | } 330 | } else { 331 | if ($this->serializationContext instanceof JMS_SerializationContext) { 332 | $context = $this->serializationContext; 333 | } else { 334 | $context = JMS_SerializationContext::create(); 335 | } 336 | } 337 | } elseif ($this->serializer instanceof SerializerInterface) { 338 | $context = $functionConfig['serialization_context'] ?? $this->serializationContext; 339 | if (!empty($context['max_depth_checks'])) { // legacy support for max_depth_checks 340 | $context['enable_max_depth'] = true; 341 | } 342 | } else { 343 | throw new \LogicException('No serializer service found in container. Please install jms/serializer-bundle or symfony/serializer.'); 344 | } 345 | 346 | return $context; 347 | } 348 | 349 | /** 350 | * Finds whether a variable is an associative array 351 | * 352 | * @param $var 353 | * @return bool 354 | */ 355 | protected function isAssoc($var) 356 | { 357 | return array_keys($var) !== range(0, count($var) - 1); 358 | } 359 | } 360 | --------------------------------------------------------------------------------