├── .gitignore ├── Tests ├── Functional │ ├── app │ │ ├── config │ │ │ ├── default.yml │ │ │ ├── webservices.json │ │ │ └── framework.yml │ │ ├── cases │ │ │ ├── JMSSerializerBundle │ │ │ │ ├── config.yml │ │ │ │ └── bundles.php │ │ │ ├── Basic │ │ │ │ ├── config.yml │ │ │ │ └── bundles.php │ │ │ └── SensioFrameworkExtraBundle │ │ │ │ ├── config.yml │ │ │ │ └── bundles.php │ │ └── AppKernel.php │ ├── GuzzleParamConverter3xTest.php │ ├── GuzzleParamConverter2xTest.php │ ├── GuzzleDataCollectorTest.php │ ├── JMSSerializerAwareCommandTest.php │ ├── JMSSerializerRequestTest.php │ ├── TestCase.php │ ├── JMSSerializerResponseTest.php │ └── AbstractGuzzleParamConverterTest.php ├── Fixtures │ ├── config │ │ ├── webservices.json │ │ ├── alt-webservices.json │ │ └── client.json │ ├── ServiceBuilder.php │ ├── ConcreteCommandClient │ │ ├── Command │ │ │ ├── GetPerson.php │ │ │ └── GetPersonWithSerializer.php │ │ └── ConcreteCommandClient.php │ └── Person.php ├── bootstrap.php ├── EventListener │ └── RequestListenerTest.php ├── DependencyInjection │ ├── MisdGuzzleExtensionTest.php │ └── ContainerTest.php ├── AbstractTestCase.php └── Service │ └── Command │ ├── JMSSerializerResponseParserTest.php │ └── LocationVisitor │ └── Request │ └── JMSSerializerBodyVisitorTest.php ├── Resources ├── doc │ ├── index.md │ ├── setup.md │ ├── service.md │ ├── plugins.md │ ├── param_converter.md │ ├── clients.md │ └── serialization.md ├── config │ ├── log.xml │ ├── param_converter.xml │ ├── service_builder.xml │ ├── monolog.xml │ ├── cache.xml │ ├── plugin.xml │ ├── services.xml │ └── serializer.xml ├── meta │ └── LICENSE └── views │ └── Collector │ └── guzzle.html.twig ├── phpunit.xml.dist ├── Service └── Command │ ├── JMSSerializerAwareCommandInterface.php │ ├── LocationVisitor │ └── Request │ │ └── JMSSerializerBodyVisitor.php │ └── JMSSerializerResponseParser.php ├── .travis.yml ├── CHANGELOG-1.0.md ├── Request └── ParamConverter │ ├── GuzzleParamConverter3x.php │ ├── GuzzleParamConverter2x.php │ └── AbstractGuzzleParamConverter.php ├── DependencyInjection ├── Compiler │ ├── MonologCompilerPass.php │ ├── ServiceBuilderCompilerPass.php │ └── ClientCompilerPass.php ├── Configuration.php └── MisdGuzzleExtension.php ├── MisdGuzzleBundle.php ├── README.md ├── composer.json ├── CHANGELOG-1.1.md ├── DataCollector └── GuzzleDataCollector.php └── EventListener ├── CommandListener.php └── RequestListener.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | phpunit.xml 4 | vendor/ 5 | -------------------------------------------------------------------------------- /Tests/Functional/app/config/default.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: framework.yml } 3 | -------------------------------------------------------------------------------- /Tests/Functional/app/cases/JMSSerializerBundle/config.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ./../../config/default.yml } 3 | -------------------------------------------------------------------------------- /Tests/Fixtures/config/webservices.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": { 3 | "foo": { 4 | "class": "Guzzle\\Service\\Client" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/Fixtures/config/alt-webservices.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": { 3 | "foo": { 4 | "class": "Guzzle\\Service\\Client" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/Functional/app/cases/Basic/config.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ./../../config/default.yml } 3 | 4 | monolog: 5 | handlers: 6 | main: 7 | type: 'null' 8 | -------------------------------------------------------------------------------- /Tests/Functional/app/config/webservices.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": { 3 | "concrete": { 4 | "class": "Misd\\GuzzleBundle\\Tests\\Fixtures\\ConcreteCommandClient\\ConcreteCommandClient" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/Functional/app/config/framework.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: test 3 | router: { resource: "%kernel.root_dir%/%kernel.test_case%/routing.yml" } 4 | test: ~ 5 | default_locale: en 6 | session: 7 | storage_id: session.storage.mock_file 8 | -------------------------------------------------------------------------------- /Resources/doc/index.md: -------------------------------------------------------------------------------- 1 | Using GuzzleBundle 2 | ================== 3 | 4 | 1. [Setting up the bundle](setup.md) 5 | 2. [Creating clients](clients.md) 6 | 3. [Service descriptions](service.md) 7 | 4. [Guzzle plugins](plugins.md) 8 | 5. [Object (de)serialization](serialization.md) 9 | 6. [Param converter](param_converter.md) 10 | -------------------------------------------------------------------------------- /Tests/Functional/app/cases/SensioFrameworkExtraBundle/config.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ./../../config/default.yml } 3 | 4 | parameters: 5 | http_client: Guzzle\Http\Client 6 | 7 | services: 8 | http_client: 9 | class: Guzzle\Http\Client 10 | tags: 11 | - { name: guzzle.client } 12 | parameter_http_client: 13 | class: %http_client% 14 | tags: 15 | - { name: guzzle.client } 16 | -------------------------------------------------------------------------------- /Tests/Fixtures/ServiceBuilder.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guzzle\Log\MonologLogAdapter 9 | Guzzle\Log\ArrayLogAdapter 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/param_converter.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./Tests/ 7 | 8 | 9 | 10 | 11 | ./ 12 | 13 | ./Resources 14 | ./Tests 15 | ./vendor 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/Fixtures/ConcreteCommandClient/Command/GetPerson.php: -------------------------------------------------------------------------------- 1 | request = $this->client->createRequest('GET', 'http://api.example.com/api/person/1'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Fixtures/ConcreteCommandClient/ConcreteCommandClient.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/../vendor/jms/serializer/src')); 28 | -------------------------------------------------------------------------------- /Resources/config/service_builder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guzzle\Service\Description\ServiceDescription 9 | 10 | 11 | 12 | 14 | %guzzle.service_builder.configuration_file% 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Resources/doc/setup.md: -------------------------------------------------------------------------------- 1 | Setting up the bundle 2 | ===================== 3 | 4 | 1. Add GuzzleBundle to your dependencies: 5 | 6 | // composer.json 7 | 8 | { 9 | // ... 10 | "require": { 11 | // ... 12 | "misd/guzzle-bundle": "~1.0" 13 | } 14 | } 15 | 16 | 2. Use Composer to download and install GuzzleBundle: 17 | 18 | $ php composer.phar update misd/guzzle-bundle 19 | 20 | 3. Register the bundle in your application: 21 | 22 | // app/AppKernel.php 23 | 24 | class AppKernel extends Kernel 25 | { 26 | // ... 27 | public function registerBundles() 28 | { 29 | $bundles = array( 30 | // ... 31 | new Misd\GuzzleBundle\MisdGuzzleBundle() 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Service/Command/JMSSerializerAwareCommandInterface.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | interface JMSSerializerAwareCommandInterface extends CommandInterface 23 | { 24 | /** 25 | * Set the JMSSerializer. 26 | * 27 | * @param SerializerInterface $serializer JMSSerializer. 28 | */ 29 | public function setSerializer(SerializerInterface $serializer); 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | - 5.6 9 | - hhvm 10 | 11 | matrix: 12 | allow_failures: 13 | - php: hhvm 14 | 15 | env: 16 | - SYMFONY_VERSION="2.2.0" GUZZLE_VERSION="3.0.0" SENSIO_FRAMEWORK_EXTRA_VERSION="~2.2" 17 | - SYMFONY_VERSION="2.3.x" GUZZLE_VERSION="~3.0" SENSIO_FRAMEWORK_EXTRA_VERSION="~2.2" 18 | - SYMFONY_VERSION="~2.4" GUZZLE_VERSION="~3.0" SENSIO_FRAMEWORK_EXTRA_VERSION="~3.0" 19 | 20 | before_install: 21 | - composer require symfony/framework-bundle:${SYMFONY_VERSION} --no-interaction --no-update 22 | - composer require guzzle/guzzle:${GUZZLE_VERSION} --no-interaction --no-update 23 | - composer require sensio/framework-extra-bundle:${SENSIO_FRAMEWORK_EXTRA_VERSION} --no-interaction --dev --no-update 24 | 25 | install: 26 | - composer install --no-interaction --dev --prefer-source 27 | 28 | script: ./vendor/bin/phpunit 29 | -------------------------------------------------------------------------------- /CHANGELOG-1.0.md: -------------------------------------------------------------------------------- 1 | Changelog for 1.0.* 2 | =================== 3 | 4 | 1.0.2 5 | ----- 6 | 7 | 12 June 2013. 8 | 9 | * Request parameters are now run through filters before trying to use the JMS Serializer. 10 | * A `NotFoundHttpException` thrown in the param converter now includes the original Guzzle exception. 11 | * The log format is now configurable. 12 | * Made compatible with Guzzle 3.6.0. 13 | * The `JMSSerializerResponseParser` now uses Guzzle's `OperationResponseParser` as a fallback rather than just the `DefaultResponseParser`. 14 | 15 | 1.0.1 16 | ----- 17 | 18 | 13 March 2013. 19 | 20 | * Fix Symfony version dependency. 21 | * Fix the profiler stopwatch from trying to be stopped when it's not running. 22 | * Monolog is now optional. 23 | * The filesystem cache path is made configurable. 24 | * The serializer can now (de)serialize arrays of objects. 25 | 26 | 1.0.0 27 | ----- 28 | 29 | 1 February 2013. 30 | 31 | * Initial release. 32 | -------------------------------------------------------------------------------- /Resources/config/monolog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | %misd_guzzle.log.format% 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/Fixtures/Person.php: -------------------------------------------------------------------------------- 1 | firstName . ' ' . $this->familyName; 22 | } 23 | 24 | /** 25 | * @Serializer\XmlAttribute 26 | * @Serializer\Type("integer") 27 | */ 28 | public $id; 29 | 30 | /** 31 | * @Serializer\SerializedName("name") 32 | * @Serializer\Type("string") 33 | */ 34 | public $firstName; 35 | 36 | /** 37 | * @Serializer\SerializedName("family-name") 38 | * @Serializer\Type("string") 39 | */ 40 | public $familyName; 41 | } 42 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2015 University of Cambridge 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Tests/Fixtures/ConcreteCommandClient/Command/GetPersonWithSerializer.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 24 | } 25 | 26 | public function process() 27 | { 28 | $this->result = $this->serializer->deserialize( 29 | $this->request->getResponse()->getBody(true), 30 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 31 | 'xml' 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Request/ParamConverter/GuzzleParamConverter3x.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class GuzzleParamConverter3x extends AbstractGuzzleParamConverter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function apply(Request $request, ParamConverter $configuration) 28 | { 29 | $this->execute($request, $configuration); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function supports(ParamConverter $configuration) 36 | { 37 | return null !== $this->find($configuration); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Request/ParamConverter/GuzzleParamConverter2x.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class GuzzleParamConverter2x extends AbstractGuzzleParamConverter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function apply(Request $request, ConfigurationInterface $configuration) 28 | { 29 | return $this->execute($request, $configuration); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function supports(ConfigurationInterface $configuration) 36 | { 37 | return null !== $this->find($configuration); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Resources/config/cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guzzle\Cache\DoctrineCacheAdapter 9 | Doctrine\Common\Cache\FilesystemCache 10 | 11 | 12 | 13 | 14 | %misd_guzzle.cache.filesystem.path% 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/MonologCompilerPass.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class MonologCompilerPass implements CompilerPassInterface 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function process(ContainerBuilder $container) 30 | { 31 | if (false === $container->has('monolog.logger') || false === $container->getParameter('misd_guzzle.log.enabled')) { 32 | return; 33 | } 34 | 35 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../../Resources/config')); 36 | 37 | $loader->load('monolog.xml'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /MisdGuzzleBundle.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class MisdGuzzleBundle extends Bundle 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function build(ContainerBuilder $container) 32 | { 33 | parent::build($container); 34 | 35 | $container->addCompilerPass(new MonologCompilerPass()); 36 | $container->addCompilerPass(new ClientCompilerPass()); 37 | $container->addCompilerPass(new ServiceBuilderCompilerPass()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Resources/doc/service.md: -------------------------------------------------------------------------------- 1 | Service descriptions 2 | ==================== 3 | 4 | > Clients created through the Guzzle service builder need to attach a service description in their `factory()` method instead. 5 | 6 | To use a Guzzle service description, first create a service for it: 7 | 8 | // MyBundle/Resources/config/services.xml 9 | 10 | 14 | %path.to.my.service_description.file% 15 | 16 | 17 | Next set the parameter for the service description file location: 18 | 19 | // MyBundle/DependencyInjection/MyBundleExtension.php 20 | 21 | $container->setParameter('path.to.my.service_description.file', __DIR__ . '/../Resources/config/client.json'); 22 | 23 | Then add the service description to your client through the `setDescription()` method: 24 | 25 | // MyBundle/Resources/config/services.xml 26 | 27 | 28 | // ... 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ServiceBuilderCompilerPass.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ServiceBuilderCompilerPass implements CompilerPassInterface 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function process(ContainerBuilder $container) 29 | { 30 | if (!$container->hasDefinition('guzzle.service_builder')) { 31 | return; 32 | } 33 | 34 | foreach ($container->findTaggedServiceIds('misd_guzzle.plugin') as $plugin => $attributes) { 35 | $container->getDefinition('guzzle.service_builder') 36 | ->addMethodCall('addGlobalPlugin', array(new Reference($plugin))); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/Functional/GuzzleParamConverter3xTest.php: -------------------------------------------------------------------------------- 1 | getClass()->getName()) { 30 | $this->markTestSkipped( 31 | 'skipping GuzzleParamConverter3xTest due to an incompatible version of the SensioFrameworkExtraBundle' 32 | ); 33 | } 34 | 35 | $this->converter = new GuzzleParamConverter3x(); 36 | 37 | parent::setUp(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/Functional/GuzzleParamConverter2xTest.php: -------------------------------------------------------------------------------- 1 | getClass()->getName()) { 30 | $this->markTestSkipped( 31 | 'skipping GuzzleParamConverter2xTest due to an incompatible version of the SensioFrameworkExtraBundle' 32 | ); 33 | } 34 | 35 | $this->converter = new GuzzleParamConverter2x(); 36 | 37 | parent::setUp(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GuzzleBundle 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/misd-service-development/guzzle-bundle.png?branch=master)](http://travis-ci.org/misd-service-development/guzzle-bundle) 5 | 6 | This bundle integrates [Guzzle 3](http://guzzle3.readthedocs.org/) into your Symfony2 application, which takes the pain out of sending HTTP requests and the redundancy out of creating web service clients. 7 | 8 | It can also integrate with the [JMSSerializerBundle](http://jmsyst.com/bundles/JMSSerializerBundle) for easy object (de)serialization, and provides a param converter for use with the [SensioFrameworkExtraBundle](http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/). 9 | 10 | Authors 11 | ------- 12 | 13 | * Chris Wilkinson 14 | * [The Symfony and Guzzle communities](https://github.com/misd-service-development/guzzle-bundle/contributors) 15 | 16 | Documentation 17 | ------------- 18 | 19 | Documentation is stored in the `Resources/doc/index.md` file in this bundle: 20 | 21 | * [Read the documentation](Resources/doc/index.md) 22 | 23 | Installation 24 | ------------ 25 | 26 | All the installation instructions are located in the [documentation](Resources/doc/index.md). 27 | 28 | Reporting an issue or a feature request 29 | --------------------------------------- 30 | 31 | Issues and feature requests are tracked in the [Github issue tracker](https://github.com/misd-service-development/guzzle-bundle/issues). 32 | -------------------------------------------------------------------------------- /Resources/config/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guzzle\Plugin\Async\AsyncPlugin 9 | Guzzle\Plugin\Backoff\BackoffPlugin 10 | Guzzle\Plugin\Cache\CachePlugin 11 | Guzzle\Plugin\Cookie\CookiePlugin 12 | Guzzle\Plugin\CurlAuth\CurlAuthPlugin 13 | Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin 14 | Guzzle\Plugin\History\HistoryPlugin 15 | Guzzle\Plugin\Log\LogPlugin 16 | Guzzle\Plugin\Md5\Md5ValidatorPlugin 17 | Guzzle\Plugin\Md5\CommandContentMd5Plugin 18 | Guzzle\Plugin\Mock\MockPlugin 19 | Guzzle\Plugin\Oauth\OauthPlugin 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "misd/guzzle-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Integrates Guzzle into your Symfony2 application", 5 | "keywords": ["guzzle", "bundle", "http", "http client", "rest", "client", "web service", "curl", "api"], 6 | "homepage": "https://github.com/misd-service-development/guzzle-bundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Chris Wilkinson", 11 | "email": "chris.wilkinson@admin.cam.ac.uk" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/misd-service-development/guzzle-bundle/issues" 16 | }, 17 | "require": { 18 | "php": ">=5.3.3", 19 | "symfony/framework-bundle": "~2.2", 20 | "guzzle/guzzle": "~3.0" 21 | }, 22 | "suggest": { 23 | "jms/serializer-bundle": "Serialize/deserialize objects to/from XML, JSON and YAML", 24 | "sensio/framework-extra-bundle": "Provides a parameter converter", 25 | "symfony/monolog-bundle": "Log requests" 26 | }, 27 | "conflict": { 28 | "jms/serializer-bundle": "<0.11-dev", 29 | "sensio/framework-extra-bundle": ">=4.0-dev" 30 | }, 31 | "require-dev": { 32 | "doctrine/cache": "~1.0", 33 | "phpunit/phpunit": "~4.3", 34 | "sensio/framework-extra-bundle": "~2.2", 35 | "symfony/yaml": "~2.2", 36 | "symfony/monolog-bundle": "~2.2", 37 | "jms/serializer-bundle": "~0.11" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Misd\\GuzzleBundle\\": "" 42 | } 43 | }, 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "1.1.x-dev" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guzzle\Service\Client 9 | Misd\GuzzleBundle\EventListener\RequestListener 10 | Misd\GuzzleBundle\DataCollector\GuzzleDataCollector 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Tests/Functional/GuzzleDataCollectorTest.php: -------------------------------------------------------------------------------- 1 | getMock('Guzzle\Log\ArrayLogAdapter'); 27 | $adapter->expects($this->any()) 28 | ->method('getLogs') 29 | ->will($this->returnValue(array( 30 | $this->newGuzzleLog(), 31 | ))); 32 | 33 | $collector = new GuzzleDataCollector($adapter); 34 | 35 | $collector->collect(new SfRequest(), new SfResponse(), null); 36 | $collector->collect(new SfRequest(), new SfResponse(), null); 37 | 38 | $this->assertCount(1, $collector->getRequests()); 39 | } 40 | 41 | public function testEmptyArrayWhenNoRequests() 42 | { 43 | $adapter = $this->getMock('Guzzle\Log\ArrayLogAdapter'); 44 | $collector = new GuzzleDataCollector($adapter); 45 | 46 | $this->assertEquals(array(), $collector->getRequests()); 47 | } 48 | 49 | private function newGuzzleLog() 50 | { 51 | return array( 52 | 'message' => '', 53 | 'extras' => array( 54 | 'response' => new GuzzleResponse(200), 55 | 'request' => new GuzzleRequest('GET', '/'), 56 | ) 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/EventListener/RequestListenerTest.php: -------------------------------------------------------------------------------- 1 | new Request('GET', '/'))); 24 | $e2 = new Event(array('request' => new Request('GET', '/'))); 25 | 26 | $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') 27 | ->disableOriginalConstructor() 28 | ->setMethods(array('start', 'stop')) 29 | ->getMock(); 30 | 31 | $stopwatch->expects($this->at(0)) 32 | ->method('start') 33 | ->with('[1] GET /'); 34 | $stopwatch->expects($this->at(1)) 35 | ->method('start') 36 | ->with('[2] GET /'); 37 | 38 | //simulate request 2 finishing before request 1. 39 | $stopwatch->expects($this->at(2)) 40 | ->method('stop') 41 | ->with('[2] GET /'); 42 | $stopwatch->expects($this->at(3)) 43 | ->method('stop') 44 | ->with('[1] GET /'); 45 | 46 | $listener = new RequestListener($stopwatch); 47 | 48 | $dispatcher = new EventDispatcher(); 49 | $dispatcher->addSubscriber($listener); 50 | 51 | $dispatcher->dispatch('request.before_send', $e); 52 | $dispatcher->dispatch('request.before_send', $e2); 53 | 54 | $dispatcher->dispatch('request.complete', $e2); 55 | $dispatcher->dispatch('request.complete', $e); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CHANGELOG-1.1.md: -------------------------------------------------------------------------------- 1 | Changelog for 1.1.* 2 | =================== 3 | 4 | 1.1.5 5 | ----- 6 | 7 | 1 December 2014. 8 | 9 | * Fix compatibility with the Symfony 2.6+ profiler when not making any requests. 10 | * Allow non-service clients to be tagged when using the param converter. 11 | * Add error response plugin class name parameter. 12 | 13 | 1.1.4 14 | ----- 15 | 16 | 17 February 2014. 17 | 18 | * Allow configuration of JMS Serializer options through the service definition's `data` property. 19 | * Remove the need to define the client when setting the command for the ParamConverter if there is only one. 20 | * Embedded profiler images and removed the public folder. 21 | * Trigger Guzzle deprecation warnings in Symfony debug mode. 22 | * Tidied up the configuration, allowing parts to be disabled and marking internal services as private. 23 | * Stop the profiler timeline from erroneously grouping requests. 24 | 25 | 1.1.3 26 | ----- 27 | 28 | 6 January 2014. 29 | 30 | * Fixed bug that prevented installation when the SensioFrameworkExtraBundle wasn't installed. 31 | 32 | 1.1.2 33 | ----- 34 | 35 | 24 December 2013. 36 | 37 | * No longer has a hard dependency on the SensioFrameworkExtraBundle. 38 | * Made compatible with both the SensioFrameworkExtraBundle 2.x and 3.x. 39 | * Allow logging to be disabled. 40 | * Show requests separately in the profiler timeline. 41 | * Prevent requests from appearing more than once on the profiler page. 42 | * Add time details to the profiler page. 43 | 44 | 1.1.1 45 | ----- 46 | 47 | 12 June 2013. 48 | 49 | * Request parameters are now run through filters before trying to use the JMS Serializer. 50 | * A `NotFoundHttpException` thrown in the param converter now includes the original Guzzle exception. 51 | * The log format is now configurable. 52 | * Made compatible with Guzzle 3.6.0. 53 | * The `JMSSerializerResponseParser` now uses Guzzle's `OperationResponseParser` as a fallback rather than just the `DefaultResponseParser`. 54 | 55 | 1.1.0 56 | ----- 57 | 58 | 13 March 2013. 59 | 60 | * First 1.1.* release. 61 | -------------------------------------------------------------------------------- /Tests/Functional/JMSSerializerAwareCommandTest.php: -------------------------------------------------------------------------------- 1 | get('guzzle.service_builder')->get('concrete'); 25 | $client->addSubscriber(self::$mock); 26 | 27 | self::$client = $client; 28 | } 29 | 30 | public function testCommandWithoutSerializer() 31 | { 32 | $client = self::$client; 33 | self::$mock->addResponse(self::xmlResponse()); 34 | 35 | $command = $client->getCommand('GetPerson'); 36 | 37 | $this->assertInstanceOf('SimpleXMLElement', $client->execute($command)); 38 | } 39 | 40 | public function testCommandWithSerializer() 41 | { 42 | $client = self::$client; 43 | self::$mock->addResponse(self::xmlResponse()); 44 | 45 | $command = $client->getCommand('GetPersonWithSerializer'); 46 | 47 | $this->assertInstanceOf('JMS\Serializer\SerializerInterface', $command->serializer); 48 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $client->execute($command)); 49 | } 50 | 51 | protected function xmlResponse($id = 1) 52 | { 53 | return Response::fromMessage( 54 | 'HTTP/1.1 200 OK 55 | Date: Wed, 25 Nov 2009 12:00:00 GMT 56 | Connection: close 57 | Server: Test 58 | Content-Type: application/xml 59 | 60 | 61 | 62 | Foo 63 | Bar 64 | 65 | ' 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/MisdGuzzleExtensionTest.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class MisdGuzzleExtensionTest extends \PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * @var ContainerBuilder 25 | */ 26 | private $container; 27 | 28 | /** 29 | * @var MisdGuzzleExtension 30 | */ 31 | private $extension; 32 | 33 | protected function setUp() 34 | { 35 | if (!version_compare(Version::VERSION, '3.6', '>=')) { 36 | $this->markTestSkipped('the emitWarning property was added in Guzzle 3.6'); 37 | } 38 | 39 | $this->container = new ContainerBuilder(); 40 | $this->extension = new MisdGuzzleExtension(); 41 | 42 | // reset the emit warnings options before each test 43 | Version::$emitWarnings = false; 44 | } 45 | 46 | public function testWithoutDebugParameter() 47 | { 48 | $this->extension->load(array(), $this->container); 49 | 50 | $this->assertFalse(Version::$emitWarnings); 51 | } 52 | 53 | public function testDebugDisabled() 54 | { 55 | $this->container->setParameter('kernel.debug', false); 56 | $this->extension->load(array(), $this->container); 57 | 58 | $this->assertFalse(Version::$emitWarnings); 59 | } 60 | 61 | public function testDebugEnabled() 62 | { 63 | $this->container->setParameter('kernel.debug', true); 64 | $this->extension->load(array(), $this->container); 65 | 66 | $this->assertTrue(Version::$emitWarnings); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Resources/config/serializer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Misd\GuzzleBundle\EventListener\CommandListener 9 | Misd\GuzzleBundle\Service\Command\LocationVisitor\Request\JMSSerializerBodyVisitor 10 | Misd\GuzzleBundle\Service\Command\JMSSerializerResponseParser 11 | Guzzle\Service\Command\OperationResponseParser 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /DataCollector/GuzzleDataCollector.php: -------------------------------------------------------------------------------- 1 | logAdapter = $logAdapter; 27 | $this->data['requests'] = array(); 28 | } 29 | 30 | public function collect(Request $request, Response $response, \Exception $exception = null) 31 | { 32 | foreach ($this->logAdapter->getLogs() as $log) { 33 | $requestId = spl_object_hash($log['extras']['request']); 34 | 35 | if (isset($this->data['requests'][$requestId])) { 36 | continue; 37 | } 38 | 39 | $datum['message'] = $log['message']; 40 | $datum['time'] = $this->getRequestTime($log['extras']['response']); 41 | $datum['request'] = (string) $log['extras']['request']; 42 | $datum['response'] = (string) $log['extras']['response']; 43 | $datum['is_error'] = $log['extras']['response']->isError(); 44 | 45 | $this->data['requests'][$requestId] = $datum; 46 | } 47 | } 48 | 49 | private function getRequestTime(GuzzleResponse $response) 50 | { 51 | $time = $response->getInfo('total_time'); 52 | 53 | if (null === $time) { 54 | $time = 0; 55 | } 56 | 57 | return (int) ($time * 1000); 58 | } 59 | 60 | public function getRequests() 61 | { 62 | return $this->data['requests']; 63 | } 64 | 65 | public function getName() 66 | { 67 | return 'guzzle'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Configuration implements ConfigurationInterface 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function getConfigTreeBuilder() 28 | { 29 | $treeBuilder = new TreeBuilder(); 30 | $rootNode = $treeBuilder->root('misd_guzzle'); 31 | 32 | $rootNode 33 | ->children() 34 | ->arrayNode('service_builder') 35 | ->canBeDisabled() 36 | ->addDefaultsIfNotSet() 37 | ->children() 38 | ->scalarNode('class')->defaultValue('Guzzle\Service\Builder\ServiceBuilder')->end() 39 | ->scalarNode('configuration_file')->defaultValue('%kernel.root_dir%/config/webservices.json')->end() 40 | ->end() 41 | ->end() 42 | ->arrayNode('filesystem_cache') 43 | ->canBeDisabled() 44 | ->addDefaultsIfNotSet() 45 | ->children() 46 | ->scalarNode('path')->defaultValue('%kernel.cache_dir%/guzzle/')->end() 47 | ->end() 48 | ->end() 49 | ->arrayNode('log') 50 | ->canBeDisabled() 51 | ->addDefaultsIfNotSet() 52 | ->children() 53 | ->scalarNode('format')->defaultValue('default')->end() 54 | ->end() 55 | ->end() 56 | ->booleanNode('serializer')->defaultTrue()->end() 57 | ->end() 58 | ; 59 | 60 | return $treeBuilder; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ClientCompilerPass.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ClientCompilerPass implements CompilerPassInterface 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function process(ContainerBuilder $container) 29 | { 30 | $plugins = $container->findTaggedServiceIds('misd_guzzle.plugin'); 31 | 32 | foreach ($container->findTaggedServiceIds('guzzle.client') as $id => $attributes) { 33 | foreach ($plugins as $plugin => $pluginAttributes) { 34 | $container->getDefinition($id)->addMethodCall('addSubscriber', array((new Reference($plugin)))); 35 | } 36 | if ('guzzle.client' !== $id && $container->hasDefinition('misd_guzzle.param_converter')) { 37 | $class = $container->getDefinition($id)->getClass(); 38 | 39 | if (true === $container->hasParameter(trim($class, '%'))) { 40 | $class = $container->getParameter(trim($class, '%')); 41 | } 42 | 43 | if ( 44 | false === class_exists($class) 45 | || 46 | ($class !== 'Guzzle\Service\ClientInterface' || false === is_subclass_of($class, 'Guzzle\Service\ClientInterface')) 47 | ) { 48 | continue; 49 | } 50 | 51 | $container->getDefinition('misd_guzzle.param_converter') 52 | ->addMethodCall('registerClient', array($id, new Reference($id))) 53 | ; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/AbstractTestCase.php: -------------------------------------------------------------------------------- 1 | getMock('Symfony\Component\HttpKernel\KernelInterface'); 27 | $kernel 28 | ->expects($this->any()) 29 | ->method('getBundles') 30 | ->will($this->returnValue(array())); 31 | } 32 | 33 | $bundle = new MisdGuzzleBundle($kernel); 34 | $extension = $bundle->getContainerExtension(); 35 | 36 | $container = new ContainerBuilder(); 37 | $container->setParameter('kernel.debug', true); 38 | $container->setParameter('kernel.cache_dir', sys_get_temp_dir() . '/guzzle'); 39 | $container->setParameter('kernel.bundles', array()); 40 | $container->setParameter('kernel.root_dir', __DIR__ . '/Fixtures'); 41 | $container->set('service_container', $container); 42 | $container->set('monolog.logger', $this->getMock('Symfony\\Bridge\\Monolog\\Logger', array(), array('app'))); 43 | $container->set('jms_serializer', $this->getMock('JMS\\Serializer\\SerializerInterface')); 44 | 45 | $container->registerExtension($extension); 46 | $extension->load($config, $container); 47 | $bundle->build($container); 48 | 49 | $container->getCompilerPassConfig()->setOptimizationPasses( 50 | array(new ResolveParameterPlaceHoldersPass(), new ResolveDefinitionTemplatesPass()) 51 | ); 52 | $container->getCompilerPassConfig()->setRemovingPasses(array()); 53 | $container->compile(); 54 | 55 | return $container; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Service/Command/JMSSerializerResponseParserTest.php: -------------------------------------------------------------------------------- 1 | setGroups('group'); 25 | $expectedContext->setVersion(1); 26 | $expectedContext->enableMaxDepthChecks(); 27 | 28 | $operation = $this->getMock('Guzzle\Service\Description\OperationInterface'); 29 | $operation->expects($this->any())->method('getResponseType')->will($this->returnValue(OperationInterface::TYPE_CLASS)); 30 | $operation->expects($this->any())->method('getResponseClass')->will($this->returnValue('ResponseClass')); 31 | 32 | $dataMap = array( 33 | array('jms_serializer.groups', 'group'), 34 | array('jms_serializer.version', 1), 35 | array('jms_serializer.max_depth_checks', true) 36 | ); 37 | 38 | $operation->expects($this->any()) 39 | ->method('getData') 40 | ->will($this->returnValueMap($dataMap)); 41 | 42 | $command = $this->getMock('Guzzle\Service\Command\CommandInterface'); 43 | $command->expects($this->any())->method('getOperation')->will($this->returnValue($operation)); 44 | 45 | $response = new Response(200);; 46 | $response->setBody('body'); 47 | 48 | $serializer = $this->getMock('JMS\Serializer\SerializerInterface'); 49 | $serializer->expects($this->once())->method('deserialize') 50 | ->with('body', 'ResponseClass', 'json', $this->equalTo($expectedContext)); 51 | 52 | $parser = new JMSSerializerResponseParser($serializer, $this->getMock('Guzzle\Service\Command\ResponseParserInterface')); 53 | 54 | $ref = new \ReflectionMethod($parser, 'deserialize'); 55 | $ref->setAccessible(true); 56 | 57 | return $ref->invoke($parser, $command, $response, 'json'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Service/Command/LocationVisitor/Request/JMSSerializerBodyVisitorTest.php: -------------------------------------------------------------------------------- 1 | setGroups('group'); 23 | $expectedContext->setVersion(1); 24 | $expectedContext->setSerializeNull(true); 25 | $expectedContext->enableMaxDepthChecks(); 26 | 27 | $parameter = $this->getMock('Guzzle\Service\Description\Parameter'); 28 | $parameter->expects($this->once())->method('getSentAs')->will($this->returnValue('json')); 29 | $parameter->expects($this->any())->method('filter')->will($this->returnValue(array())); 30 | 31 | $dataMap = array( 32 | array('jms_serializer.groups', 'group'), 33 | array('jms_serializer.version', 1), 34 | array('jms_serializer.serialize_nulls', true), 35 | array('jms_serializer.max_depth_checks', true) 36 | ); 37 | 38 | $parameter->expects($this->any()) 39 | ->method('getData') 40 | ->will($this->returnValueMap($dataMap)); 41 | 42 | $command = $this->getMock('Guzzle\Service\Command\CommandInterface'); 43 | $request = $this->getMockBuilder('Guzzle\Http\Message\EntityEnclosingRequest') 44 | ->disableOriginalConstructor() 45 | ->getMock(); 46 | 47 | $serializer = $this->getMock('JMS\Serializer\SerializerInterface'); 48 | $serializer->expects($this->once())->method('serialize') 49 | ->with(array(), 'json', $this->equalTo($expectedContext)) 50 | ->will($this->returnValue('serialized')); 51 | 52 | $parser = new JMSSerializerBodyVisitor($serializer, $this->getMock('Guzzle\Service\Command\ResponseParserInterface')); 53 | 54 | $ref = new \ReflectionMethod($parser, 'visit'); 55 | $ref->setAccessible(true); 56 | 57 | return $ref->invoke($parser, $command, $request, $parameter, 'value'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /EventListener/CommandListener.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class CommandListener implements EventSubscriberInterface 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public static function getSubscribedEvents() 33 | { 34 | return array( 35 | 'client.command.create' => 'onCommandCreate', 36 | ); 37 | } 38 | 39 | /** 40 | * @var SerializerInterface 41 | */ 42 | protected $serializer; 43 | 44 | /** 45 | * @var RequestVisitorInterface 46 | */ 47 | protected $requestBodyVisitor; 48 | 49 | /** 50 | * @var ResponseParserInterface 51 | */ 52 | protected $responseParser; 53 | 54 | /** 55 | * Constructor. 56 | * 57 | * @param SerializerInterface|null $serializer JMSSerializer, if available. 58 | * @param RequestVisitorInterface $requestBodyVisitor Request body visitor. 59 | * @param ResponseParserInterface $responseParser Response parser. 60 | */ 61 | public function __construct( 62 | SerializerInterface $serializer = null, 63 | RequestVisitorInterface $requestBodyVisitor, 64 | ResponseParserInterface $responseParser 65 | ) 66 | { 67 | $this->serializer = $serializer; 68 | $this->requestBodyVisitor = $requestBodyVisitor; 69 | $this->responseParser = $responseParser; 70 | } 71 | 72 | /** 73 | * Add JMSSerializerBundle-enabled services to commands. 74 | * 75 | * @param Event $event Event. 76 | */ 77 | public function onCommandCreate(Event $event) 78 | { 79 | if ($event['command'] instanceof OperationCommand) { 80 | $event['command']->getRequestSerializer()->addVisitor('body', $this->requestBodyVisitor); 81 | $event['command']->setResponseParser($this->responseParser); 82 | } 83 | 84 | if ($event['command'] instanceof JMSSerializerAwareCommandInterface && null !== $this->serializer) { 85 | $event['command']->setSerializer($this->serializer); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/Functional/app/AppKernel.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class AppKernel extends Kernel 24 | { 25 | private $testCase; 26 | private $rootConfig; 27 | 28 | public function __construct($testCase, $rootConfig, $environment, $debug) 29 | { 30 | if (!is_dir(__DIR__ . '/cases/' . $testCase)) { 31 | throw new \InvalidArgumentException(sprintf('The test case "%s" does not exist.', $testCase)); 32 | } 33 | $this->testCase = $testCase; 34 | 35 | $fs = new Filesystem(); 36 | if (!$fs->isAbsolutePath($rootConfig) && !file_exists( 37 | $rootConfig = __DIR__ . '/cases/' . $testCase . '/' . $rootConfig 38 | ) 39 | ) { 40 | throw new \InvalidArgumentException(sprintf('The root config "%s" does not exist.', $rootConfig)); 41 | } 42 | $this->rootConfig = $rootConfig; 43 | 44 | parent::__construct($environment . '_' . $testCase, $debug); 45 | } 46 | 47 | public function registerBundles() 48 | { 49 | if (!file_exists($filename = $this->getRootDir() . '/cases/' . $this->testCase . '/bundles.php')) { 50 | throw new \RuntimeException(sprintf('The bundles file "%s" does not exist.', $filename)); 51 | } 52 | 53 | return include $filename; 54 | } 55 | 56 | public function init() 57 | { 58 | } 59 | 60 | public function getRootDir() 61 | { 62 | return __DIR__; 63 | } 64 | 65 | public function getCacheDir() 66 | { 67 | return sys_get_temp_dir() . '/' . Kernel::VERSION . '/' . $this->testCase . '/cache/' . $this->environment; 68 | } 69 | 70 | public function getLogDir() 71 | { 72 | return sys_get_temp_dir() . '/' . Kernel::VERSION . '/' . $this->testCase . '/logs'; 73 | } 74 | 75 | public function registerContainerConfiguration(LoaderInterface $loader) 76 | { 77 | $loader->load($this->rootConfig); 78 | } 79 | 80 | public function serialize() 81 | { 82 | return serialize(array($this->testCase, $this->rootConfig, $this->getEnvironment(), $this->isDebug())); 83 | } 84 | 85 | public function unserialize($str) 86 | { 87 | call_user_func_array(array($this, '__construct'), unserialize($str)); 88 | } 89 | 90 | protected function getKernelParameters() 91 | { 92 | $parameters = parent::getKernelParameters(); 93 | $parameters['kernel.test_case'] = $this->testCase; 94 | 95 | return $parameters; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Resources/doc/plugins.md: -------------------------------------------------------------------------------- 1 | Guzzle plugins 2 | ============== 3 | 4 | > Clients created through the Guzzle service builder need to attach plugins in their `factory()` method instead. 5 | 6 | Guzzle plugins can be created and attached to your clients easily in your bundle. 7 | 8 | For example, to attach the `CurlAuth` plugin to your client first create a service for the plugin: 9 | 10 | // MyBundle/Resources/config/services.xml 11 | 12 | 13 | %example.client.curl_auth.username% 14 | %example.client.curl_auth.password% 15 | 16 | 17 | Next set the username and password parameters from your application's configuration: 18 | 19 | // MyBundle/DependencyInjection/MyBundleExtension.php 20 | 21 | $container->setParameter('example.client.curl_auth.username', $config['username']); 22 | $container->setParameter('example.client.curl_auth.password', $config['password']); 23 | 24 | Then attach the plugin service to your client service through the `addSubscriber()` method: 25 | 26 | // MyBundle/Resources/config/services.xml 27 | 28 | 29 | // ... 30 | 31 | 32 | 33 | 34 | 35 | Parameters 36 | ---------- 37 | 38 | The bundle provides parameters for each of the standard Guzzle plugin classes. 39 | 40 | - `%guzzle.plugin.async.class%` 41 | - `%guzzle.plugin.backoff.class%` 42 | - `%guzzle.plugin.cache.class%` 43 | - `%guzzle.plugin.cookie.class%` 44 | - `%guzzle.plugin.curl_auth.class%` 45 | - `%guzzle.plugin.history.class%` 46 | - `%guzzle.plugin.log.class%` 47 | - `%guzzle.plugin.md5_validator.class%` 48 | - `%guzzle.plugin.command_content_md5.class%` 49 | - `%guzzle.plugin.mock.class%` 50 | - `%guzzle.plugin.oauth.class%` 51 | 52 | Logging 53 | ------- 54 | 55 | Guzzle clients are automatically connected to your Symfony2 application's Monolog through the `misd_guzzle.log.monolog` service. 56 | 57 | Log format can be customized through the configuration: 58 | 59 | // app/config.yml 60 | misd_guzzle: 61 | log: 62 | format: debug # default/debug/short or custom MessageFormatter string 63 | 64 | Caching 65 | ------- 66 | 67 | The bundle provides the `misd_guzzle.cache.filesystem` service, which allows you to quickly take advantage of caching. Simply add the service to your client: 68 | 69 | // MyBundle/Resources/config/services.xml 70 | 71 | 72 | // ... 73 | 74 | 75 | 76 | 77 | 78 | This will be slower than using, say, Memcache, but doesn't require any dependencies. 79 | 80 | By default the files are stored in `app/cache/{env}/guzzle/`. This can be changed in your configuration: 81 | 82 | // app/config.yml 83 | 84 | misd_guzzle: 85 | filesystem_cache: 86 | path: "%kernel.cache_dir%/my_folder/" 87 | -------------------------------------------------------------------------------- /Service/Command/LocationVisitor/Request/JMSSerializerBodyVisitor.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class JMSSerializerBodyVisitor extends BodyVisitor 27 | { 28 | /** 29 | * Serializer. 30 | * 31 | * @var SerializerInterface|null 32 | */ 33 | protected $serializer; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param SerializerInterface|null $serializer Serializer, or null if not used. 39 | */ 40 | public function __construct(SerializerInterface $serializer = null) 41 | { 42 | $this->serializer = $serializer; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) 49 | { 50 | $filteredValue = $param->filter($value); 51 | 52 | if (null !== $this->serializer && (is_object($filteredValue) || is_array($filteredValue))) { 53 | switch ($param->getSentAs()) { 54 | case 'json': 55 | $request->setHeader('Content-Type', 'application/json'); 56 | $contentType = 'json'; 57 | break; 58 | case 'yml': 59 | case 'yaml': 60 | $request->setHeader('Content-Type', 'application/yaml'); 61 | $contentType = 'yml'; 62 | break; 63 | default: 64 | $request->setHeader('Content-Type', 'application/xml'); 65 | $contentType = 'xml'; 66 | break; 67 | } 68 | $context = SerializationContext::create(); 69 | 70 | if (null !== $groups = $param->getData('jms_serializer.groups')) { 71 | $context->setGroups($groups); 72 | } 73 | if (null !== $version = $param->getData('jms_serializer.version')) { 74 | $context->setVersion($version); 75 | } 76 | if (null !== $nulls = $param->getData('jms_serializer.serialize_nulls')) { 77 | $context->setSerializeNull($nulls); 78 | } 79 | if (true === $param->getData('jms_serializer.max_depth_checks')) { 80 | $context->enableMaxDepthChecks(); 81 | } 82 | 83 | $value = $this->serializer->serialize($filteredValue, $contentType, $context); 84 | } 85 | 86 | parent::visit($command, $request, $param, $value); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /EventListener/RequestListener.php: -------------------------------------------------------------------------------- 1 | 23 | * @author Ben Davies 24 | */ 25 | class RequestListener implements EventSubscriberInterface 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public static function getSubscribedEvents() 31 | { 32 | return array( 33 | 'request.before_send' => array('onRequestBeforeSend', 0), 34 | 'request.complete' => array('onRequestComplete', 255), 35 | ); 36 | } 37 | 38 | /** 39 | * @var Stopwatch|null 40 | */ 41 | protected $stopwatch; 42 | 43 | /** 44 | * Cache of request hashes against their open order 45 | * 46 | * @var array 47 | */ 48 | protected $requests = array(); 49 | 50 | /** 51 | * Constructor. 52 | * 53 | * @param Stopwatch|null $stopwatch 54 | */ 55 | public function __construct(Stopwatch $stopwatch = null) 56 | { 57 | $this->stopwatch = $stopwatch; 58 | } 59 | 60 | /** 61 | * Starts the stopwatch. 62 | * 63 | * @param Event $e 64 | */ 65 | public function onRequestBeforeSend(Event $e) 66 | { 67 | if (null !== $this->stopwatch) { 68 | $this->start($e); 69 | } 70 | } 71 | 72 | /** 73 | * Stops the stopwatch. 74 | * @param Event $e 75 | */ 76 | public function onRequestComplete(Event $e) 77 | { 78 | if (null !== $this->stopwatch) { 79 | $this->stop($e); 80 | } 81 | } 82 | 83 | /** 84 | * @param Event $e 85 | */ 86 | private function start(Event $e) 87 | { 88 | $request = $e['request']; 89 | $this->requests[$this->hash($request)] = count($this->requests) + 1; 90 | $name = $this->getEventName($request); 91 | 92 | $this->stopwatch->start($name, 'guzzle'); 93 | } 94 | 95 | /** 96 | * @param Event $e 97 | */ 98 | private function stop(Event $e) 99 | { 100 | $request = $e['request']; 101 | $name = $this->getEventName($request); 102 | 103 | $this->stopwatch->stop($name); 104 | } 105 | 106 | /** 107 | * @param Request $request 108 | * 109 | * @return string 110 | */ 111 | private function hash(Request $request) 112 | { 113 | return spl_object_hash($request); 114 | } 115 | 116 | /** 117 | * @param Request $request 118 | * 119 | * @return string 120 | */ 121 | private function getEventName(Request $request) 122 | { 123 | return sprintf('[%d] %s %s', $this->requests[$this->hash($request)], $request->getMethod(), urldecode($request->getPath())); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Resources/doc/param_converter.md: -------------------------------------------------------------------------------- 1 | Param converter 2 | =============== 3 | 4 | Service clients that have been initialised through the Symfony2 service container and have their [responses deserialized by the JMSSerializerBundle](serialization.html) can make use of the bundle's [parameter converter](http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html). 5 | 6 | The syntax is based on the Doctrine param converter. 7 | 8 | The SensioFrameworkExtraBundle needs to be installed separately. 9 | 10 | Basic usage 11 | ----------- 12 | 13 | The simpliest way is to just type-hint your controller method: 14 | 15 | /** 16 | * @Route("blog/{id}") 17 | */ 18 | public function showAction(Post $post) 19 | 20 | The bundle searches through your clients and finds a `GET` command that has its `responseClass` set as the required class (`Vendor\MyBundle\Post`). If it finds one, it will execute it using the route parameters (ie `{id}`). 21 | 22 | A successful response (ie the deserialized `Vendor\MyBundle\Post`) is injected into the method arguments. If the web service returns a `404` response, a Symfony2 exception (`Symfony\Component\HttpKernel\Exception\NotFoundHttpException`) is thrown; with any other abnormal web service response, the normal Guzzle exception is thrown. 23 | 24 | You can configure the conversion in a `@ParamConverter` annotation. 25 | 26 | Defining the command/client 27 | --------------------------- 28 | 29 | If you have more than one command available that returns the class that you're after, you can explictly set with client and command to use through the `client` and `command` options. 30 | 31 | For example, to force use of the `example.client`'s `GetPost` command: 32 | 33 | /** 34 | * @Route("blog/{id}") 35 | * @ParamConverter("post", options={"client"="example.client", "command"="GetPost"}) 36 | */ 37 | public function showAction(Post $post) 38 | 39 | If you just set the client it will search for a command that matches: 40 | 41 | /** 42 | * @Route("blog/{id}") 43 | * @ParamConverter("post", options={"client"="example.client"}) 44 | */ 45 | public function showAction(Post $post) 46 | 47 | If you have a single client, you may omit the `client` option: 48 | 49 | /** 50 | * @Route("blog/{id}") 51 | * @ParamConverter("post", options={"command"="GetPost"}) 52 | */ 53 | public function showAction(Post $post) 54 | 55 | Parameter mapping 56 | ----------------- 57 | 58 | If your route parameter does not have the same name as your command's paramater, you can use the `mapping` option. 59 | 60 | For example, to map the route's `post_id` to the command's `id` parameter: 61 | 62 | /** 63 | * @Route("blog/{post_id}") 64 | * @ParamConverter("post", options={"mapping": {"id": "post_id"}}) 65 | */ 66 | public function showAction(Post $post) 67 | 68 | This then allows you to have mutiple converters in one action: 69 | 70 | /** 71 | * @Route("blog/{id}/comments/{comment_id}") 72 | * @ParamConverter("comment", options={"mapping": {"id": "comment_id"}}) 73 | */ 74 | public function showAction(Post $post, Comment $comment) 75 | 76 | If you need to exclude a route parameter from being used: 77 | 78 | /** 79 | * @Route("blog/{date}/{slug}") 80 | * @ParamConverter("post", options={"exclude": {"date"}}) 81 | */ 82 | public function showAction(Post $post, \DateTime $date) 83 | -------------------------------------------------------------------------------- /Resources/doc/clients.md: -------------------------------------------------------------------------------- 1 | Creating clients 2 | ================ 3 | 4 | Guzzle clients can be created in 2 ways: 5 | 6 | 1. Using the Symfony2 service container (recommended). 7 | 2. Using the Guzzle service builder. 8 | 9 | Using the Symfony2 service container 10 | ------------------------------------ 11 | 12 | The best way to use Guzzle in Symfony2 is to let the service container create your client objects for you. Create a service for each client in your bundle, and tag it with `guzzle.client`. If you aren't using a concrete client class, you can use the default Guzzle client through the `%guzzle.client.class%` parameter: 13 | 14 | // MyBundle/Resources/config/services.xml 15 | 16 | 17 | 18 | http://api.example.com/ 19 | 20 | 21 | Using the service container allows you to easily set up your client. You can add a collection of settings in the second argument (see the Guzzle docs for details), and you can call methods to [attach plugins](plugins.md) etc. For example: 22 | 23 | // MyBundle/Resources/config/services.xml 24 | 25 | 26 | 27 | http://api.example.com/ 28 | 29 | true 30 | false 31 | 32 | 33 | 34 | 35 | 36 | My Guzzle client 37 | true 38 | 39 | 40 | 41 | You can then access your service through the container. For example, in a controller: 42 | 43 | $client = $this->get('example.client'); 44 | 45 | ### Generic client 46 | 47 | A generic Guzzle client is available in the `guzzle.client` service: 48 | 49 | $client = $this->get('guzzle.client'); 50 | 51 | You could use this to access absolute URLs: 52 | 53 | $request = $client->get('http://www.example.com/'); 54 | 55 | Using the Guzzle service builder 56 | -------------------------------- 57 | 58 | The bundle also allows clients to be created through the Guzzle service builder. Add a reference to your service builder data file in your application's config file (default values shown): 59 | 60 | // app/config.yml 61 | 62 | misd_guzzle: 63 | service_builder: 64 | class: "Guzzle\Service\Builder\ServiceBuilder" 65 | configuration_file: "%kernel.root_dir%/config/webservices.json" 66 | 67 | Clients created through the Guzzle service builder can be accessed through the `guzzle.service_builder` service: 68 | 69 | $client = $this->get('guzzle.service_builder')->get('my_client'); 70 | 71 | As service builder clients do not have access to the Symfony2 service container, you will need to attach the Guzzle service description and any plugins directly in your client's `factory` method: 72 | 73 | public static function factory($config = array()) { 74 | // ... 75 | $client->setDescription(__DIR__ . '/client.json'); 76 | 77 | $authPlugin = new \Guzzle\Plugin\CurlAuth\CurlAuthPlugin('username', 'password'); 78 | $client->addSubscriber($authPlugin); 79 | -------------------------------------------------------------------------------- /Tests/Functional/JMSSerializerRequestTest.php: -------------------------------------------------------------------------------- 1 | getCommand('AddPerson', array('person' => self::person1())); 23 | $request = $command->prepare(); 24 | 25 | $this->assertEquals('application/xml', $request->getHeader('Content-Type')); 26 | $this->assertEquals( 27 | $request->getBody(), 28 | self::getContainer('JMSSerializerBundle')->get('serializer')->serialize(self::person1(), 'xml') 29 | ); 30 | } 31 | 32 | public function testGetPersonJsonRequestWithSerializer() 33 | { 34 | $client = self::getClient('JMSSerializerBundle'); 35 | $command = $client->getCommand('AddPersonJson', array('person' => self::person1())); 36 | $request = $command->prepare(); 37 | 38 | $this->assertEquals('application/json', $request->getHeader('Content-Type')); 39 | $this->assertEquals( 40 | $request->getBody(), 41 | self::getContainer('JMSSerializerBundle')->get('serializer')->serialize(self::person1(), 'json') 42 | ); 43 | } 44 | 45 | public function testGetPersonYamlRequestWithSerializer() 46 | { 47 | $client = self::getClient('JMSSerializerBundle'); 48 | $command = $client->getCommand('AddPersonYaml', array('person' => self::person1())); 49 | self::$mock->addResponse(new Response(201)); 50 | $request = $command->prepare(); 51 | 52 | $this->assertEquals('application/yaml', $request->getHeader('Content-Type')); 53 | $this->assertEquals( 54 | $request->getBody(), 55 | self::getContainer('JMSSerializerBundle')->get('serializer')->serialize(self::person1(), 'yml') 56 | ); 57 | } 58 | 59 | public function testUpdatePeopleRequest() 60 | { 61 | $people = array(self::person1(), self::person2()); 62 | 63 | $client = self::getClient('JMSSerializerBundle'); 64 | 65 | $command = $client->getCommand('UpdatePeopleJson', array('people' => $people)); 66 | $request = $command->prepare(); 67 | 68 | $this->assertEquals('application/json', $request->getHeader('Content-Type')); 69 | $this->assertEquals( 70 | $request->getBody(), 71 | self::getContainer('JMSSerializerBundle')->get('serializer')->serialize($people, 'json') 72 | ); 73 | } 74 | 75 | public function testUpdatePeopleWithFilterRequest() 76 | { 77 | $people = array('foo' => 'bar', array('baz', 'qux')); 78 | 79 | $client = self::getClient('JMSSerializerBundle'); 80 | 81 | $command = $client->getCommand('UpdatePeopleJsonWithFilter', array('people' => $people)); 82 | $request = $command->prepare(); 83 | 84 | $this->assertEquals($request->getBody(), json_encode($people)); 85 | } 86 | 87 | protected function person1() 88 | { 89 | $person = new Person(); 90 | $person->id = 1; 91 | $person->firstName = 'Foo'; 92 | $person->familyName = 'Bar'; 93 | 94 | return $person; 95 | } 96 | 97 | protected function person2() 98 | { 99 | $person = new Person(); 100 | $person->id = 2; 101 | $person->firstName = 'Baz'; 102 | $person->familyName = 'Qux'; 103 | 104 | return $person; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /DependencyInjection/MisdGuzzleExtension.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class MisdGuzzleExtension extends Extension 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function load(array $configs, ContainerBuilder $container) 31 | { 32 | $configuration = new Configuration(); 33 | $config = $this->processConfiguration($configuration, $configs); 34 | 35 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 36 | $loader->load('services.xml'); 37 | $loader->load('plugin.xml'); 38 | $loader->load('log.xml'); 39 | 40 | if ($config['serializer']) { 41 | $loader->load('serializer.xml'); 42 | } 43 | 44 | if (interface_exists('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface')) { 45 | // choose a ParamConverterInterface implementation that is compatible 46 | // with the version of SensioFrameworkExtraBundle being used 47 | $parameter = new \ReflectionParameter( 48 | array( 49 | 'Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface', 50 | 'supports', 51 | ), 52 | 'configuration' 53 | ); 54 | if ('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter' == $parameter->getClass()->getName()) { 55 | $container->setParameter( 56 | 'misd_guzzle.param_converter.class', 57 | 'Misd\GuzzleBundle\Request\ParamConverter\GuzzleParamConverter3x' 58 | ); 59 | } else { 60 | $container->setParameter( 61 | 'misd_guzzle.param_converter.class', 62 | 'Misd\GuzzleBundle\Request\ParamConverter\GuzzleParamConverter2x' 63 | ); 64 | } 65 | $loader->load('param_converter.xml'); 66 | } 67 | 68 | if ($config['service_builder']['enabled']) { 69 | $loader->load('service_builder.xml'); 70 | $container->setParameter( 71 | 'guzzle.service_builder.class', 72 | $config['service_builder']['class'] 73 | ); 74 | $container->setParameter( 75 | 'guzzle.service_builder.configuration_file', 76 | $config['service_builder']['configuration_file'] 77 | ); 78 | } 79 | 80 | if ($config['filesystem_cache']['enabled']) { 81 | $loader->load('cache.xml'); 82 | $container->setParameter('misd_guzzle.cache.filesystem.path', $config['filesystem_cache']['path']); 83 | } 84 | 85 | $logFormat = $config['log']['format']; 86 | if (in_array($logFormat, array('default', 'debug', 'short'))) { 87 | $logFormat = constant(sprintf('Guzzle\Log\MessageFormatter::%s_FORMAT', strtoupper($logFormat))); 88 | } 89 | $container->setParameter('misd_guzzle.log.format', $logFormat); 90 | $container->setParameter('misd_guzzle.log.enabled', $config['log']['enabled']); 91 | 92 | if ( 93 | version_compare(Version::VERSION, '3.6', '>=') 94 | && $container->hasParameter('kernel.debug') 95 | ) { 96 | Version::$emitWarnings = $container->getParameter('kernel.debug'); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Tests/Functional/TestCase.php: -------------------------------------------------------------------------------- 1 | $testCase)); 35 | } 36 | 37 | self::$mock = new MockPlugin(); 38 | 39 | foreach (self::getTestCases() as $testCase) { 40 | $client = self::getContainer($testCase)->get('guzzle.client'); 41 | $client->setDescription(ServiceDescription::factory(__DIR__ . '/../Fixtures/config/client.json')); 42 | $client->addSubscriber(self::$mock); 43 | self::$clients[$testCase] = $client; 44 | } 45 | } 46 | 47 | public static function tearDownAfterClass() 48 | { 49 | foreach (self::getTestCases() as $testCase) { 50 | self::deleteTmpDir($testCase); 51 | } 52 | 53 | parent::tearDownAfterClass(); 54 | } 55 | 56 | /** 57 | * @var Kernel[] 58 | */ 59 | private static $kernels = array(); 60 | 61 | protected static function getKernel($testCase) 62 | { 63 | if (false === isset(self::$kernels[$testCase])) { 64 | throw new InvalidArgumentException(sprintf('Unknown testCase %s', $testCase)); 65 | } 66 | 67 | return self::$kernels[$testCase]; 68 | } 69 | 70 | protected static function getContainer($testCase) 71 | { 72 | if (false === isset(self::$kernels[$testCase])) { 73 | throw new InvalidArgumentException(sprintf('Unknown testCase %s', $testCase)); 74 | } 75 | 76 | return self::$kernels[$testCase]->getContainer(); 77 | } 78 | 79 | private static function deleteTmpDir($testCase) 80 | { 81 | if (!file_exists($dir = sys_get_temp_dir() . '/' . Kernel::VERSION . '/' . $testCase)) { 82 | return; 83 | } 84 | 85 | $fs = new Filesystem(); 86 | $fs->remove($dir); 87 | } 88 | 89 | private static function createKernel(array $options = array()) 90 | { 91 | if (!isset($options['test_case'])) { 92 | throw new \InvalidArgumentException('The option "test_case" must be set.'); 93 | } 94 | 95 | self::deleteTmpDir($options['test_case']); 96 | 97 | $kernel = new \Misd\GuzzleBundle\Tests\Functional\app\AppKernel( 98 | $options['test_case'], 99 | isset($options['root_config']) ? $options['root_config'] : 'config.yml', 100 | isset($options['environment']) ? $options['environment'] : 'guzzlebundletest', 101 | isset($options['debug']) ? $options['debug'] : true 102 | ); 103 | 104 | $kernel->boot(); 105 | 106 | return $kernel; 107 | } 108 | 109 | /** 110 | * @var Client[] 111 | */ 112 | protected static $clients = array(); 113 | /** 114 | * @var MockPlugin 115 | */ 116 | protected static $mock; 117 | 118 | protected static function getClient($testCase) 119 | { 120 | if (false === isset(self::$clients[$testCase])) { 121 | throw new InvalidArgumentException(sprintf('Unknown testCase %s', $testCase)); 122 | } 123 | 124 | return self::$clients[$testCase]; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /Tests/Fixtures/config/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test", 3 | "baseUrl": "http://api.example.com/api/", 4 | "operations": { 5 | "GetPerson": { 6 | "httpMethod": "GET", 7 | "uri": "person/{id}", 8 | "summary": "Get a person", 9 | "parameters": { 10 | "id": { 11 | "location": "query", 12 | "description": "Person to retrieve by ID", 13 | "type": "integer", 14 | "required": true 15 | } 16 | } 17 | }, 18 | "GetPersonClass": { 19 | "httpMethod": "GET", 20 | "uri": "person/{id}", 21 | "summary": "Get a person", 22 | "responseClass": "Misd\\GuzzleBundle\\Tests\\Fixtures\\Person", 23 | "parameters": { 24 | "id": { 25 | "location": "query", 26 | "description": "Person to retrieve by ID", 27 | "type": "integer", 28 | "required": true 29 | } 30 | } 31 | }, 32 | "GetPersonInvalidClass": { 33 | "httpMethod": "GET", 34 | "uri": "person/{id}", 35 | "summary": "Get a person", 36 | "responseClass": "Misd\\GuzzleBundle\\Tests\\Fixtures\\NonExistentPerson", 37 | "parameters": { 38 | "id": { 39 | "location": "query", 40 | "description": "Person to retrieve by ID", 41 | "type": "integer", 42 | "required": true 43 | } 44 | } 45 | }, 46 | "GetPersonClassArray": { 47 | "httpMethod": "GET", 48 | "uri": "person", 49 | "summary": "Get an array of people", 50 | "responseClass": "array" 51 | }, 52 | "AddPerson": { 53 | "httpMethod": "POST", 54 | "uri": "person", 55 | "summary": "Add a person", 56 | "parameters": { 57 | "person": { 58 | "location": "body", 59 | "type": "object", 60 | "instanceOf": "Misd\\GuzzleBundle\\Tests\\Fixtures\\Person", 61 | "required": "true" 62 | } 63 | } 64 | }, 65 | "AddPersonJson": { 66 | "httpMethod": "POST", 67 | "uri": "person", 68 | "summary": "Add a person", 69 | "parameters": { 70 | "person": { 71 | "location": "body", 72 | "type": "object", 73 | "instanceOf": "Misd\\GuzzleBundle\\Tests\\Fixtures\\Person", 74 | "sentAs": "json", 75 | "required": "true" 76 | } 77 | } 78 | }, 79 | "AddPersonYaml": { 80 | "httpMethod": "POST", 81 | "uri": "person", 82 | "summary": "Add a person", 83 | "parameters": { 84 | "person": { 85 | "location": "body", 86 | "type": "object", 87 | "instanceOf": "Misd\\GuzzleBundle\\Tests\\Fixtures\\Person", 88 | "sentAs": "yml", 89 | "required": "true" 90 | } 91 | } 92 | }, 93 | "UpdatePeopleJson": { 94 | "httpMethod": "POST", 95 | "uri": "updatePeople", 96 | "summary": "Update people", 97 | "parameters": { 98 | "people": { 99 | "location": "body", 100 | "type": "array", 101 | "sentAs": "json", 102 | "required": "true" 103 | } 104 | } 105 | }, 106 | "UpdatePeopleJsonWithFilter": { 107 | "httpMethod": "POST", 108 | "uri": "updatePeople", 109 | "summary": "Update people", 110 | "parameters": { 111 | "people": { 112 | "location": "body", 113 | "type": "array", 114 | "filters": ["json_encode"], 115 | "required": "true" 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Service/Command/JMSSerializerResponseParser.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class JMSSerializerResponseParser implements ResponseParserInterface 27 | { 28 | /** 29 | * Serializer. 30 | * 31 | * @var SerializerInterface|null 32 | */ 33 | protected $serializer; 34 | 35 | /** 36 | * Fallback parser. 37 | * 38 | * @var ResponseParserInterface 39 | */ 40 | protected $fallback; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param SerializerInterface|null $serializer Serializer, or null if not used. 46 | * @param ResponseParserInterface $fallback Fallback parser to use if the serializer cannot parse the response. 47 | */ 48 | public function __construct(SerializerInterface $serializer = null, ResponseParserInterface $fallback) 49 | { 50 | $this->serializer = $serializer; 51 | $this->fallback = $fallback; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function parse(CommandInterface $command) 58 | { 59 | $response = $command->getRequest()->getResponse(); 60 | $contentType = (string) $response->getHeader('Content-Type'); 61 | 62 | return $this->handleParsing($command, $response, $contentType); 63 | } 64 | 65 | /** 66 | * Handle the parsing. 67 | * 68 | * @param CommandInterface $command Command. 69 | * @param Response $response Response. 70 | * @param string $contentType Content type. 71 | * 72 | * @return mixed Returns the result to set on the command. 73 | */ 74 | protected function handleParsing(CommandInterface $command, Response $response, $contentType) 75 | { 76 | $deserialized = $this->deserialize($command, $response, $contentType); 77 | 78 | if (null !== $deserialized) { 79 | return $deserialized; 80 | } else { 81 | return $this->fallback->parse($command); 82 | } 83 | } 84 | 85 | /** 86 | * Deserialize the response. 87 | * 88 | * @param CommandInterface $command Command. 89 | * @param Response $response Response. 90 | * @param string $contentType Content type. 91 | * 92 | * @return mixed|null Deserialized response, or `null`. 93 | */ 94 | protected function deserialize(CommandInterface $command, Response $response, $contentType) 95 | { 96 | if ($this->serializer) { 97 | if (false !== stripos($contentType, 'json')) { 98 | $serializerContentType = 'json'; 99 | } elseif (false !== stripos($contentType, 'xml')) { 100 | $serializerContentType = 'xml'; 101 | } else { 102 | $serializerContentType = null; 103 | } 104 | 105 | if (null !== $serializerContentType && 106 | OperationInterface::TYPE_CLASS === $command->getOperation()->getResponseType() 107 | ) { 108 | $context = DeserializationContext::create(); 109 | $operation = $command->getOperation(); 110 | 111 | if (null !== $groups = $operation->getData('jms_serializer.groups')) { 112 | $context->setGroups($groups); 113 | } 114 | if (null !== $version = $operation->getData('jms_serializer.version')) { 115 | $context->setVersion($version); 116 | } 117 | if (true === $operation->getData('jms_serializer.max_depth_checks')) { 118 | $context->enableMaxDepthChecks(); 119 | } 120 | 121 | return $this->serializer->deserialize( 122 | $response->getBody(), 123 | $command->getOperation()->getResponseClass(), 124 | $serializerContentType, 125 | $context 126 | ); 127 | } 128 | } 129 | 130 | return null; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Resources/doc/serialization.md: -------------------------------------------------------------------------------- 1 | Object (de)serialization 2 | ======================== 3 | 4 | The bundle integrates with the [JMSSerializerBundle](http://jmsyst.com/bundles/JMSSerializerBundle), allowing you to easily work with concrete objects without having to create concrete commands. 5 | 6 | The JMSSerializerBundle needs to be installed separately. 7 | 8 | Responses 9 | --------- 10 | 11 | To turn a response into an object (ie deserialize it), set the `responseClass` value in your command as a fully-qualified class name. 12 | 13 | For example: 14 | 15 | "GetPerson":{ 16 | "httpMethod":"GET", 17 | "uri":"person/{id}", 18 | "summary":"Gets a person", 19 | "responseClass":"Vendor\\MyBundle\\Entity\\Person", 20 | "parameters":{ 21 | "id":{ 22 | "location":"uri", 23 | "type":"integer", 24 | "description":"Person to retrieve by ID", 25 | "required":"true" 26 | } 27 | } 28 | } 29 | 30 | Executing the `GetPerson` command will now return an instance of `Vendor\MyBundle\Entity\Person`: 31 | 32 | $command = $client->getCommand('GetPerson', array('id' => $id)); 33 | $person = $client->execute($command); 34 | 35 | If you wish to customize the deserialization context of the serializer, you can do so by usage of the `data` property of the Operation. Groups, version, and max depth checks are configurable for deserialization: 36 | 37 | For example: 38 | 39 | "GetPerson":{ 40 | "httpMethod":"GET", 41 | "uri":"person/{id}", 42 | "summary":"Gets a person", 43 | "responseClass":"Vendor\\MyBundle\\Entity\\Person", 44 | "data": { 45 | "jms_serializer.groups": "person-details", 46 | "jms_serializer.version": 1, 47 | "jms_serializer.max_depth_checks": true, 48 | } 49 | "parameters":{ 50 | "id":{ 51 | "location":"uri", 52 | "type":"integer", 53 | "description":"Person to retrieve by ID", 54 | "required":"true" 55 | } 56 | } 57 | } 58 | 59 | ### Arrays 60 | 61 | The `responseClass` value can actually be any of the JMS Serializer's `@Type` annotation value forms which contain a classname. 62 | 63 | For example, to deserialize a JSON array that doesn't have a root element: 64 | 65 | "GetPeople":{ 66 | "httpMethod":"GET", 67 | "uri":"people", 68 | "summary":"Gets an array of people", 69 | "responseClass":"array" 70 | } 71 | 72 | Executing the `GetPeople` command will now return an array of `Vendor\MyBundle\Entity\Person` instances: 73 | 74 | $command = $client->getCommand('GetPeople'); 75 | $people = $client->execute($command); 76 | 77 | Requests 78 | -------- 79 | 80 | To send a (serialized) object in your request, put your object in a `body` parameter. You should also set the `instanceOf` value in the parameter as the fully-qualified class name. 81 | 82 | By default it will serialize the object into XML. The change this, set the `sentAs` value as a format that the JMSSerializerBundle can use (ie `json`, `yml` or `xml`). 83 | 84 | For example: 85 | 86 | "CreatePerson":{ 87 | "httpMethod":"POST", 88 | "uri":"person", 89 | "summary":"Create a person", 90 | "parameters":{ 91 | "person":{ 92 | "location":"body", 93 | "type":"object", 94 | "instanceOf":"Vendor\\MyBundle\\Entity\\Person", 95 | "sentAs":"json", 96 | "required":"true" 97 | } 98 | } 99 | } 100 | 101 | Executing the `CreatePerson` command will now send an instance of `Vendor\MyBundle\Entity\Person` as JSON: 102 | 103 | $command = $client->getCommand('CreatePerson', array('person' => $person)); 104 | $client->execute($command); 105 | 106 | If you wish to customize the serialization context of the serializer, you can do so by usage of the `data` property of the Parameter. Groups, version, serializing nulls and max depth checks are configurable for serialization: 107 | 108 | For example: 109 | 110 | "CreatePerson":{ 111 | "httpMethod":"POST", 112 | "uri":"person", 113 | "summary":"Create a person", 114 | "parameters":{ 115 | "person":{ 116 | "location":"body", 117 | "type":"object", 118 | "instanceOf":"Vendor\\MyBundle\\Entity\\Person", 119 | "sentAs":"json", 120 | "required":"true", 121 | "data": { 122 | "jms_serializer.groups": "person-details", 123 | "jms_serializer.version": 1, 124 | "jms_serializer.serialize_nulls": true, 125 | "jms_serializer.max_depth_checks": true, 126 | } 127 | } 128 | } 129 | } 130 | 131 | Concrete commands 132 | ----------------- 133 | 134 | When using a concrete command, you can still make use of the JMSSerializer by implementating `Misd\GuzzleBundle\Service\Command\JMSSerializerAwareCommandInterface`. 135 | -------------------------------------------------------------------------------- /Resources/views/Collector/guzzle.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'WebProfilerBundle:Profiler:layout.html.twig' %} 2 | 3 | {% block toolbar %} 4 | {% set icon %} 5 | Guzzle 7 | {{ collector.requests|length }} 8 | {% endset %} 9 | {% set text %} 10 |
11 | HTTP Requests 12 | {{ collector.requests|length }} 13 |
14 | {% endset %} 15 | {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %} 16 | {% endblock %} 17 | 18 | {% block menu %} 19 | 20 | 21 | Guzzle 22 | 23 | {{ collector.requests|length }} 24 | 25 | 26 | {% endblock %} 27 | 28 | {% block panel %} 29 |

HTTP requests

30 | 31 | {% if collector.requests|length == 0 %} 32 |

33 | No requests sent. 34 |

35 | {% else %} 36 |
    37 | {% for i, request in collector.requests %} 38 |
  • 39 |
    40 | 42 | + 44 | - 46 | 47 | {{ request.message }} ({{ request.time|number_format }} ms) 48 |
    49 | 50 | 60 |
  • 61 | {% endfor %} 62 |
63 | {% endif %} 64 | 65 | 77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /Request/ParamConverter/AbstractGuzzleParamConverter.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | abstract class AbstractGuzzleParamConverter implements ParamConverterInterface 28 | { 29 | /** 30 | * @var ClientInterface[] 31 | */ 32 | private $clients = array(); 33 | 34 | /** 35 | * Make the param converter aware of a client. 36 | * 37 | * @param string $id 38 | * @param ClientInterface $client 39 | */ 40 | public function registerClient($id, ClientInterface $client) 41 | { 42 | $this->clients[$id] = $client; 43 | } 44 | 45 | /** 46 | * Stores the object in the request. 47 | * 48 | * @param Request $request The request 49 | * @param ParamConverter $configuration Contains the name, class and options of the object 50 | * 51 | * @return boolean True if the object has been successfully set, else false 52 | * 53 | * @throws NotFoundHttpException 54 | * @throws BadResponseException 55 | */ 56 | protected function execute(Request $request, ParamConverter $configuration) 57 | { 58 | $found = $this->find($configuration); 59 | 60 | $name = $configuration->getName(); 61 | $class = $configuration->getClass(); 62 | $options = $configuration->getOptions(); 63 | 64 | $client = $found['client']; 65 | $command = $found['command']; 66 | $operation = $client->getDescription()->getOperation($command); 67 | 68 | $routeParameters = $request->attributes->get('_route_params'); 69 | 70 | if (true === isset($options['exclude'])) { 71 | foreach ($options['exclude'] as $exclude) { 72 | unset($routeParameters[$exclude]); 73 | } 74 | } 75 | 76 | if (false === isset($options['mapping'])) { 77 | $options['parameters'] = $routeParameters; 78 | } else { 79 | $options['parameters'] = array(); 80 | foreach ($options['mapping'] as $key => $parameter) { 81 | $options['parameters'][$parameter] = $routeParameters[$key]; 82 | } 83 | } 84 | 85 | $parameters = array(); 86 | 87 | foreach ($options['parameters'] as $key => $value) { 88 | if ($operation->hasParam($key)) { 89 | switch ($operation->getParam($key)->getType()) { 90 | case 'integer': 91 | $value = (int) $value; 92 | } 93 | $parameters[$key] = $value; 94 | } 95 | } 96 | 97 | $command = $client->getCommand($command, $parameters); 98 | 99 | try { 100 | $result = $client->execute($command); 101 | } catch (BadResponseException $e) { 102 | if (true === $configuration->isOptional()) { 103 | $result = null; 104 | } elseif (404 === $e->getResponse()->getStatusCode()) { 105 | throw new NotFoundHttpException(sprintf('%s object not found.', $class), $e); 106 | } else { 107 | throw $e; 108 | } 109 | } 110 | 111 | $request->attributes->set($name, $result); 112 | 113 | return true; 114 | } 115 | 116 | /** 117 | * Try and find a command for the requested class. 118 | * 119 | * @param ParamConverter $configuration 120 | * 121 | * @return array|null Array containing the client and command if found, null if not. 122 | * 123 | * @throws LogicException 124 | */ 125 | protected function find(ParamConverter $configuration) 126 | { 127 | $options = $configuration->getOptions(); 128 | 129 | // determine the client, if possible 130 | if (true === isset($options['client'])) { 131 | if (false === isset($this->clients[$options['client']])) { 132 | throw new LogicException(sprintf('Unknown client \'%s\'', $options['client'])); 133 | } 134 | $client = $this->clients[$options['client']]; 135 | } elseif (1 === count($this->clients)) { 136 | $ids = array_keys($this->clients); 137 | $options['client'] = reset($ids); 138 | $client = reset($this->clients); 139 | } else { 140 | $client = null; 141 | } 142 | 143 | // determine the command, if possible 144 | if (true === isset($options['command'])) { 145 | if (null === $client) { 146 | throw new LogicException('Command defined without a client'); 147 | } 148 | $operations = $client->getDescription()->getOperations(); 149 | if (false === isset($operations[$options['command']])) { 150 | throw new LogicException(sprintf( 151 | 'Unknown command \'%s\' for client \'%s\'', 152 | $options['command'], 153 | $options['client'] 154 | )); 155 | } 156 | 157 | $command = $options['command']; 158 | 159 | if (false === ($configuration->getClass() === $operations[$options['command']]->getResponseClass())) { 160 | throw new LogicException(sprintf( 161 | 'Command \'%s\' return \'%s\' rather than \'%s\'', 162 | $options['command'], 163 | $operations[$options['command']]->getResponseClass(), 164 | $configuration->getClass() 165 | )); 166 | } 167 | } else { 168 | $command = null; 169 | } 170 | 171 | // if we don't know the command yet, try and find it 172 | if (null === $command) { 173 | if (null !== $client) { 174 | $searchClients = array($options['client'] => $client); 175 | } else { 176 | $searchClients = $this->clients; 177 | } 178 | foreach ($searchClients as $thisClient) { 179 | if (null === $thisClient->getDescription()) { 180 | continue; 181 | } 182 | 183 | foreach ($thisClient->getDescription()->getOperations() as $operation) { 184 | if ( 185 | 'GET' === $operation->getHttpMethod() && 186 | $configuration->getClass() === $operation->getResponseClass() 187 | ) { 188 | $client = $thisClient; 189 | $command = $operation->getName(); 190 | 191 | break 2; 192 | } 193 | } 194 | } 195 | } 196 | 197 | if (null !== $client && null !== $command) { 198 | return array('client' => $client, 'command' => $command); 199 | } else { 200 | return null; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Tests/Functional/JMSSerializerResponseTest.php: -------------------------------------------------------------------------------- 1 | getCommand('GetPerson', array('id' => 1)); 24 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 25 | 26 | $this->assertInstanceOf('SimpleXMLElement', $client->execute($command)); 27 | } 28 | 29 | public function testGetPersonXmlResponseWithoutSerializer() 30 | { 31 | $client = self::getClient('Basic'); 32 | $command = $client->getCommand('GetPerson', array('id' => 1)); 33 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 34 | 35 | $this->assertInstanceOf('SimpleXMLElement', $client->execute($command)); 36 | } 37 | 38 | public function testGetPersonJsonResponseWithSerializer() 39 | { 40 | $client = self::getClient('JMSSerializerBundle'); 41 | $command = $client->getCommand('GetPerson', array('id' => 1)); 42 | self::$mock->addResponse(Response::fromMessage(self::jsonResponse())); 43 | 44 | $this->assertTrue(is_array($client->execute($command))); 45 | } 46 | 47 | public function testGetPersonJsonResponseWithoutSerializer() 48 | { 49 | $client = self::getClient('Basic'); 50 | $command = $client->getCommand('GetPerson', array('id' => 1)); 51 | self::$mock->addResponse(Response::fromMessage(self::jsonResponse())); 52 | 53 | $this->assertTrue(is_array($client->execute($command))); 54 | } 55 | 56 | public function testGetPersonUnknownResponseWithSerializer() 57 | { 58 | $client = self::getClient('JMSSerializerBundle'); 59 | $command = $client->getCommand('GetPerson', array('id' => 1)); 60 | self::$mock->addResponse(Response::fromMessage(self::unknownResponse())); 61 | 62 | $this->assertInstanceOf('Guzzle\Http\Message\Response', $client->execute($command)); 63 | } 64 | 65 | public function testGetPersonUnknownResponseWithoutSerializer() 66 | { 67 | $client = self::getClient('Basic'); 68 | $command = $client->getCommand('GetPerson', array('id' => 1)); 69 | self::$mock->addResponse(Response::fromMessage(self::unknownResponse())); 70 | 71 | $this->assertInstanceOf('Guzzle\Http\Message\Response', $client->execute($command)); 72 | } 73 | 74 | public function testGetPersonClassXmlResponseWithSerializer() 75 | { 76 | $client = self::getClient('JMSSerializerBundle'); 77 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 78 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 79 | $person = $client->execute($command); 80 | 81 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $person); 82 | $this->assertEquals(1, $person->id); 83 | $this->assertEquals('Foo', $person->firstName); 84 | $this->assertEquals('Bar', $person->familyName); 85 | } 86 | 87 | public function testGetPersonClassXmlResponseWithoutSerializer() 88 | { 89 | $client = self::getClient('Basic'); 90 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 91 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 92 | 93 | if (version_compare(Version::VERSION, '3.3.0', '>=')) { 94 | $this->setExpectedException('Guzzle\Service\Exception\ResponseClassException'); 95 | } 96 | 97 | $this->assertInstanceOf('SimpleXMLElement', $client->execute($command)); 98 | } 99 | 100 | public function testGetPersonClassJsonResponseWithSerializer() 101 | { 102 | $client = self::getClient('JMSSerializerBundle'); 103 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 104 | self::$mock->addResponse(Response::fromMessage(self::jsonResponse())); 105 | $person = $client->execute($command); 106 | 107 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $person); 108 | $this->assertEquals(1, $person->id); 109 | $this->assertEquals('Foo', $person->firstName); 110 | $this->assertEquals('Bar', $person->familyName); 111 | } 112 | 113 | public function testGetPersonClassJsonResponseWithoutSerializer() 114 | { 115 | $client = self::getClient('Basic'); 116 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 117 | self::$mock->addResponse(Response::fromMessage(self::jsonResponse())); 118 | 119 | if (version_compare(Version::VERSION, '3.3.0', '>=')) { 120 | $this->setExpectedException('Guzzle\Service\Exception\ResponseClassException'); 121 | } 122 | 123 | $this->assertTrue(is_array($client->execute($command))); 124 | } 125 | 126 | public function testGetPersonClassUnknownResponseWithSerializer() 127 | { 128 | $client = self::getClient('JMSSerializerBundle'); 129 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 130 | self::$mock->addResponse(Response::fromMessage(self::unknownResponse())); 131 | 132 | if (version_compare(Version::VERSION, '3.3.0', '>=')) { 133 | $this->setExpectedException('Guzzle\Service\Exception\ResponseClassException'); 134 | } 135 | 136 | $this->assertInstanceOf('Guzzle\Http\Message\Response', $client->execute($command)); 137 | } 138 | 139 | public function testGetPersonClassUnknownResponseWithoutSerializer() 140 | { 141 | $client = self::getClient('Basic'); 142 | $command = $client->getCommand('GetPersonClass', array('id' => 1)); 143 | self::$mock->addResponse(Response::fromMessage(self::unknownResponse())); 144 | 145 | if (version_compare(Version::VERSION, '3.3.0', '>=')) { 146 | $this->setExpectedException('Guzzle\Service\Exception\ResponseClassException'); 147 | } 148 | 149 | $this->assertInstanceOf('Guzzle\Http\Message\Response', $client->execute($command)); 150 | } 151 | 152 | /** 153 | * @expectedException \Exception 154 | */ 155 | public function testGetPersonInvalidClassXmlResponseWithSerializer() 156 | { 157 | $client = self::getClient('JMSSerializerBundle'); 158 | $command = $client->getCommand('GetPersonInvalidClass', array('id' => 1)); 159 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 160 | $client->execute($command); 161 | } 162 | 163 | public function testGetPersonInvalidClassXmlResponseWithoutSerializer() 164 | { 165 | $client = self::getClient('Basic'); 166 | $command = $client->getCommand('GetPersonInvalidClass', array('id' => 1)); 167 | self::$mock->addResponse(Response::fromMessage(self::xmlResponse())); 168 | 169 | if (version_compare(Version::VERSION, '3.3.0', '>=')) { 170 | $this->setExpectedException('Guzzle\Service\Exception\ResponseClassException'); 171 | } 172 | 173 | $this->assertInstanceOf('SimpleXMLElement', $client->execute($command)); 174 | } 175 | 176 | public function testGetPersonClassArrayJsonResponseWithSerializer() 177 | { 178 | $client = self::getClient('JMSSerializerBundle'); 179 | $command = $client->getCommand('GetPersonClassArray'); 180 | self::$mock->addResponse(Response::fromMessage(self::jsonArrayResponse())); 181 | $people = $client->execute($command); 182 | 183 | $this->assertTrue(is_array($people)); 184 | $this->assertCount(2, $people); 185 | 186 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $people[0]); 187 | $this->assertEquals(1, $people[0]->id); 188 | $this->assertEquals('Foo', $people[0]->firstName); 189 | $this->assertEquals('Bar', $people[0]->familyName); 190 | 191 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $people[1]); 192 | $this->assertEquals(2, $people[1]->id); 193 | $this->assertEquals('Baz', $people[1]->firstName); 194 | $this->assertEquals('Qux', $people[1]->familyName); 195 | } 196 | 197 | protected function xmlResponse() 198 | { 199 | return << 207 | 208 | Foo 209 | Bar 210 | 211 | EOT; 212 | } 213 | 214 | protected function jsonResponse() 215 | { 216 | return <<converter->registerClient('without.description', new Client()); 29 | $this->converter->registerClient('with.description', self::getClient('JMSSerializerBundle')); 30 | } 31 | 32 | public function testSupports() 33 | { 34 | $config = $this->createConfiguration(__CLASS__); 35 | $this->assertFalse($this->converter->supports($config)); 36 | 37 | $config = $this->createConfiguration('Misd\GuzzleBundle\Tests\Fixtures\Person'); 38 | $this->assertTrue($this->converter->supports($config)); 39 | 40 | $config = $this->createConfiguration( 41 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 42 | array('client' => 'with.description') 43 | ); 44 | $this->assertTrue($this->converter->supports($config)); 45 | 46 | $config = $this->createConfiguration( 47 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 48 | array('client' => 'with.description', 'command' => 'GetPersonClass') 49 | ); 50 | $this->assertTrue($this->converter->supports($config)); 51 | } 52 | 53 | /** 54 | * @expectedException \LogicException 55 | */ 56 | public function testUnknownClient() 57 | { 58 | $config = $this->createConfiguration( 59 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 60 | array('client' => 'unknown.client') 61 | ); 62 | $this->converter->supports($config); 63 | } 64 | 65 | /** 66 | * @expectedException \LogicException 67 | */ 68 | public function testCommandWithIndeterminableClient() 69 | { 70 | $config = $this->createConfiguration( 71 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 72 | array('command' => 'UnknownCommand') 73 | ); 74 | $this->converter->supports($config); 75 | } 76 | 77 | public function testCommandWithSingleClient() 78 | { 79 | $class = get_class($this->converter); 80 | $converter = new $class; 81 | $converter->registerClient('with.description', self::getClient('JMSSerializerBundle')); 82 | 83 | $config = $this->createConfiguration( 84 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 85 | array('command' => 'GetPersonClass') 86 | ); 87 | 88 | $this->assertTrue($converter->supports($config)); 89 | } 90 | 91 | /** 92 | * @expectedException \LogicException 93 | * @expectedExceptionMessage Unknown command 'UnknownCommand' for client 'with.description' 94 | */ 95 | public function testCommandWithSingleClientWithUnknownCommand() 96 | { 97 | $class = get_class($this->converter); 98 | $converter = new $class; 99 | $converter->registerClient('with.description', self::getClient('JMSSerializerBundle')); 100 | 101 | $config = $this->createConfiguration( 102 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 103 | array('command' => 'UnknownCommand') 104 | ); 105 | 106 | $converter->supports($config); 107 | } 108 | 109 | /** 110 | * @expectedException \LogicException 111 | */ 112 | public function testUnknownCommand() 113 | { 114 | $config = $this->createConfiguration( 115 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 116 | array('client' => 'with.description', 'command' => 'UnknownCommand') 117 | ); 118 | $this->converter->supports($config); 119 | } 120 | 121 | /** 122 | * @expectedException \LogicException 123 | */ 124 | public function testCommandReturnsWrongClass() 125 | { 126 | $config = $this->createConfiguration( 127 | __CLASS__, 128 | array('client' => 'with.description', 'command' => 'GetPersonClass') 129 | ); 130 | $this->converter->supports($config); 131 | } 132 | 133 | public function testApply() 134 | { 135 | $request = new Request(); 136 | $request->attributes->set('_route_params', array('id' => 1)); 137 | $config = $this->createConfiguration('Misd\GuzzleBundle\Tests\Fixtures\Person', array(), 'arg'); 138 | 139 | self::$mock->addResponse(self::response200()); 140 | 141 | $this->converter->apply($request, $config); 142 | 143 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $request->attributes->get('arg')); 144 | } 145 | 146 | public function testApplyWithMappingAndExclude() 147 | { 148 | $request = new Request(); 149 | 150 | $request->attributes->set('_route_params', array('real-id' => 2, 'id' => 1)); 151 | $config = $this->createConfiguration( 152 | 'Misd\GuzzleBundle\Tests\Fixtures\Person', 153 | array('mapping' => array('real-id' => 'id'), 'exclude' => array('id')), 154 | 'arg' 155 | ); 156 | 157 | self::$mock->addResponse(self::response200(2)); 158 | 159 | $this->converter->apply($request, $config); 160 | 161 | $this->assertInstanceOf('Misd\GuzzleBundle\Tests\Fixtures\Person', $request->attributes->get('arg')); 162 | $this->assertEquals(2, $request->attributes->get('arg')->id); 163 | } 164 | 165 | /** 166 | * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 167 | */ 168 | public function testApplyWith404() 169 | { 170 | $request = new Request(); 171 | $request->attributes->set('_route_params', array('id' => 1)); 172 | $config = $this->createConfiguration('Misd\GuzzleBundle\Tests\Fixtures\Person', array(), 'arg'); 173 | 174 | self::$mock->addResponse(self::response404()); 175 | 176 | $this->converter->apply($request, $config); 177 | } 178 | 179 | public function testApplyWith404WhenOptional() 180 | { 181 | $request = new Request(); 182 | $request->attributes->set('_route_params', array('id' => 1)); 183 | $config = $this->createConfiguration('Misd\GuzzleBundle\Tests\Fixtures\Person', array(), 'arg', true); 184 | 185 | self::$mock->addResponse(self::response404()); 186 | 187 | $this->converter->apply($request, $config); 188 | 189 | $this->assertNull($request->attributes->get('arg')); 190 | } 191 | 192 | /** 193 | * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException 194 | */ 195 | public function testApplyWith500() 196 | { 197 | $request = new Request(); 198 | $request->attributes->set('_route_params', array('id' => 1)); 199 | $config = $this->createConfiguration('Misd\GuzzleBundle\Tests\Fixtures\Person', array(), 'arg'); 200 | 201 | self::$mock->addResponse(self::response500()); 202 | 203 | $this->converter->apply($request, $config); 204 | } 205 | 206 | public function testIgnoresNonServiceClients() { 207 | $paramConverter = self::getContainer('SensioFrameworkExtraBundle')->get('misd_guzzle.param_converter'); 208 | 209 | $this->assertAttributeEmpty('clients', $paramConverter); 210 | } 211 | 212 | /** 213 | * @param null $class 214 | * @param array $options 215 | * @param string $name 216 | * @param bool $isOptional 217 | * 218 | * @return ConfigurationInterface 219 | */ 220 | protected function createConfiguration($class = null, array $options = null, $name = 'arg', $isOptional = false) 221 | { 222 | $config = $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter') 223 | ->disableOriginalConstructor() 224 | ->setMethods( 225 | array( 226 | 'getClass', 227 | 'getAliasName', 228 | 'getOptions', 229 | 'getName', 230 | 'isOptional', 231 | 'allowArray', 232 | ) 233 | ) 234 | ->getMock() 235 | ; 236 | if ($class !== null) { 237 | $config->expects($this->any()) 238 | ->method('getClass') 239 | ->will($this->returnValue($class)); 240 | } 241 | if ($options !== null) { 242 | $config->expects($this->any()) 243 | ->method('getOptions') 244 | ->will($this->returnValue($options)); 245 | } 246 | $config->expects($this->any()) 247 | ->method('getName') 248 | ->will($this->returnValue($name)); 249 | $config->expects($this->any()) 250 | ->method('isOptional') 251 | ->will($this->returnValue($isOptional)); 252 | 253 | return $config; 254 | } 255 | 256 | protected function response200($id = 1) 257 | { 258 | return Response::fromMessage( 259 | 'HTTP/1.1 200 OK 260 | Date: Wed, 25 Nov 2009 12:00:00 GMT 261 | Connection: close 262 | Server: Test 263 | Content-Type: application/xml 264 | 265 | 266 | 267 | Foo 268 | Bar 269 | 270 | ' 271 | ); 272 | } 273 | 274 | protected function response404() 275 | { 276 | return Response::fromMessage('HTTP/1.1 404 Not Found'); 277 | } 278 | 279 | protected function response500() 280 | { 281 | return Response::fromMessage('HTTP/1.1 500 Internal Server Error'); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/ContainerTest.php: -------------------------------------------------------------------------------- 1 | getContainer(array($config)); 25 | 26 | // core 27 | 28 | $this->assertTrue($container->has('guzzle.client')); 29 | $this->assertInstanceOf('Guzzle\Service\Client', $container->get('guzzle.client')); 30 | $this->assertTrue($container->getDefinition('guzzle.client')->hasTag('guzzle.client')); 31 | 32 | $this->assertTrue($container->hasParameter('guzzle.service_builder.class')); 33 | $this->assertTrue($container->has('guzzle.service_builder')); 34 | $this->assertInstanceOf( 35 | 'Guzzle\Service\Builder\ServiceBuilderInterface', 36 | $container->get('guzzle.service_builder') 37 | ); 38 | 39 | $this->assertTrue($container->has('misd_guzzle.response.parser')); 40 | $this->assertInstanceOf( 41 | 'Misd\GuzzleBundle\Service\Command\LocationVisitor\Request\JMSSerializerBodyVisitor', 42 | $container->get('misd_guzzle.request.visitor.body') 43 | ); 44 | 45 | $this->assertTrue($container->has('misd_guzzle.response.parser')); 46 | $this->assertInstanceOf( 47 | 'Misd\GuzzleBundle\Service\Command\JMSSerializerResponseParser', 48 | $container->get('misd_guzzle.response.parser') 49 | ); 50 | 51 | $this->assertTrue($container->has('misd_guzzle.log.array')); 52 | $this->assertInstanceOf('Guzzle\Plugin\Log\LogPlugin', $container->get('misd_guzzle.log.array')); 53 | $this->assertTrue($container->getDefinition('misd_guzzle.log.array')->hasTag('misd_guzzle.plugin')); 54 | 55 | $this->assertTrue($container->has('misd_guzzle.log.adapter.array')); 56 | $this->assertInstanceOf('Guzzle\Log\ArrayLogAdapter', $container->get('misd_guzzle.log.adapter.array')); 57 | 58 | $this->assertTrue($container->has('misd_guzzle.data_collector')); 59 | $this->assertInstanceOf( 60 | 'Misd\GuzzleBundle\DataCollector\GuzzleDataCollector', 61 | $container->get('misd_guzzle.data_collector') 62 | ); 63 | $this->assertTrue($container->getDefinition('misd_guzzle.data_collector')->hasTag('data_collector')); 64 | 65 | $this->assertTrue($container->has('misd_guzzle.listener.request_listener')); 66 | $this->assertInstanceOf( 67 | 'Misd\GuzzleBundle\EventListener\RequestListener', 68 | $container->get('misd_guzzle.listener.request_listener') 69 | ); 70 | $this->assertTrue( 71 | $container->getDefinition('misd_guzzle.listener.request_listener')->hasTag('misd_guzzle.plugin') 72 | ); 73 | 74 | $this->assertTrue($container->has('misd_guzzle.listener.command_listener')); 75 | $this->assertInstanceOf( 76 | 'Misd\GuzzleBundle\EventListener\CommandListener', 77 | $container->get('misd_guzzle.listener.command_listener') 78 | ); 79 | $this->assertTrue( 80 | $container->getDefinition('misd_guzzle.listener.command_listener')->hasTag('misd_guzzle.plugin') 81 | ); 82 | 83 | $this->assertTrue($container->has('misd_guzzle.param_converter')); 84 | 85 | // the misd_guzzle.param_converter implementation depends on the 86 | // version of the SensioFrameworkExtraBundle 87 | $parameter = new \ReflectionParameter( 88 | array( 89 | 'Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface', 90 | 'supports', 91 | ), 92 | 'configuration' 93 | ); 94 | if ('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter' == $parameter->getClass()->getName()) { 95 | $this->assertInstanceOf( 96 | 'Misd\GuzzleBundle\Request\ParamConverter\GuzzleParamConverter3x', 97 | $container->get('misd_guzzle.param_converter') 98 | ); 99 | } else { 100 | $this->assertInstanceOf( 101 | 'Misd\GuzzleBundle\Request\ParamConverter\GuzzleParamConverter2x', 102 | $container->get('misd_guzzle.param_converter') 103 | ); 104 | } 105 | 106 | // monolog 107 | 108 | $this->assertTrue($container->has('misd_guzzle.log.monolog')); 109 | $this->assertInstanceOf('Guzzle\Plugin\Log\LogPlugin', $container->get('misd_guzzle.log.monolog')); 110 | $this->assertTrue($container->getDefinition('misd_guzzle.log.monolog')->hasTag('misd_guzzle.plugin')); 111 | $this->assertEquals(MessageFormatter::DEFAULT_FORMAT, $container->getDefinition('misd_guzzle.log.monolog')->getArgument(1)); 112 | 113 | $this->assertTrue($container->has('misd_guzzle.log.adapter.monolog')); 114 | $this->assertInstanceOf('Guzzle\Log\MonologLogAdapter', $container->get('misd_guzzle.log.adapter.monolog')); 115 | $this->assertTrue($container->getDefinition('misd_guzzle.log.adapter.monolog')->hasTag('monolog.logger')); 116 | 117 | // plugin 118 | 119 | $this->assertTrue($container->hasParameter('guzzle.plugin.async.class')); 120 | $this->assertEquals('Guzzle\Plugin\Async\AsyncPlugin', $container->getParameter('guzzle.plugin.async.class')); 121 | 122 | $this->assertTrue($container->hasParameter('guzzle.plugin.backoff.class')); 123 | $this->assertEquals( 124 | 'Guzzle\Plugin\Backoff\BackoffPlugin', 125 | $container->getParameter('guzzle.plugin.backoff.class') 126 | ); 127 | 128 | $this->assertTrue($container->hasParameter('guzzle.plugin.cache.class')); 129 | $this->assertEquals('Guzzle\Plugin\Cache\CachePlugin', $container->getParameter('guzzle.plugin.cache.class')); 130 | 131 | $this->assertTrue($container->hasParameter('guzzle.plugin.cookie.class')); 132 | $this->assertEquals( 133 | 'Guzzle\Plugin\Cookie\CookiePlugin', 134 | $container->getParameter('guzzle.plugin.cookie.class') 135 | ); 136 | 137 | $this->assertTrue($container->hasParameter('guzzle.plugin.curl_auth.class')); 138 | $this->assertEquals( 139 | 'Guzzle\Plugin\CurlAuth\CurlAuthPlugin', 140 | $container->getParameter('guzzle.plugin.curl_auth.class') 141 | ); 142 | 143 | $this->assertTrue($container->hasParameter('guzzle.plugin.history.class')); 144 | $this->assertEquals( 145 | 'Guzzle\Plugin\History\HistoryPlugin', 146 | $container->getParameter('guzzle.plugin.history.class') 147 | ); 148 | 149 | $this->assertTrue($container->hasParameter('guzzle.plugin.log.class')); 150 | $this->assertEquals('Guzzle\Plugin\Log\LogPlugin', $container->getParameter('guzzle.plugin.log.class')); 151 | 152 | $this->assertTrue($container->hasParameter('guzzle.plugin.md5_validator.class')); 153 | $this->assertEquals( 154 | 'Guzzle\Plugin\Md5\Md5ValidatorPlugin', 155 | $container->getParameter('guzzle.plugin.md5_validator.class') 156 | ); 157 | 158 | $this->assertTrue($container->hasParameter('guzzle.plugin.command_content_md5.class')); 159 | $this->assertEquals( 160 | 'Guzzle\Plugin\Md5\CommandContentMd5Plugin', 161 | $container->getParameter('guzzle.plugin.command_content_md5.class') 162 | ); 163 | 164 | $this->assertTrue($container->hasParameter('guzzle.plugin.mock.class')); 165 | $this->assertEquals('Guzzle\Plugin\Mock\MockPlugin', $container->getParameter('guzzle.plugin.mock.class')); 166 | 167 | $this->assertTrue($container->hasParameter('guzzle.plugin.oauth.class')); 168 | $this->assertEquals('Guzzle\Plugin\Oauth\OauthPlugin', $container->getParameter('guzzle.plugin.oauth.class')); 169 | 170 | // log 171 | 172 | $this->assertTrue($container->hasParameter('guzzle.log.adapter.monolog.class')); 173 | $this->assertEquals( 174 | 'Guzzle\Log\MonologLogAdapter', 175 | $container->getParameter('guzzle.log.adapter.monolog.class') 176 | ); 177 | 178 | $this->assertTrue($container->hasParameter('guzzle.log.adapter.array.class')); 179 | $this->assertEquals('Guzzle\Log\ArrayLogAdapter', $container->getParameter('guzzle.log.adapter.array.class')); 180 | 181 | // cache 182 | 183 | $this->assertTrue($container->hasParameter('guzzle.cache.doctrine.class')); 184 | $this->assertEquals( 185 | 'Guzzle\Cache\DoctrineCacheAdapter', 186 | $container->getParameter('guzzle.cache.doctrine.class') 187 | ); 188 | 189 | $this->assertTrue($container->hasParameter('guzzle.cache.doctrine.filesystem.class')); 190 | $this->assertEquals( 191 | 'Doctrine\Common\Cache\FilesystemCache', 192 | $container->getParameter('guzzle.cache.doctrine.filesystem.class') 193 | ); 194 | 195 | $this->assertTrue($container->has('misd_guzzle.cache.doctrine.filesystem.adapter')); 196 | $this->assertInstanceOf( 197 | 'Doctrine\Common\Cache\FilesystemCache', 198 | $container->get('misd_guzzle.cache.doctrine.filesystem.adapter') 199 | ); 200 | 201 | $this->assertTrue($container->has('misd_guzzle.cache.doctrine.filesystem')); 202 | $this->assertInstanceOf( 203 | 'Guzzle\Cache\DoctrineCacheAdapter', 204 | $container->get('misd_guzzle.cache.doctrine.filesystem') 205 | ); 206 | 207 | $this->assertTrue($container->has('misd_guzzle.cache.filesystem')); 208 | $this->assertInstanceOf('Guzzle\Plugin\Cache\CachePlugin', $container->get('misd_guzzle.cache.filesystem')); 209 | } 210 | 211 | public function configs() 212 | { 213 | $configs = array(); 214 | 215 | $configs[] = array(); 216 | $configs[] = array('guzzle' => array('service_builder' => array('class' => 'Misd\GuzzleBundle\Tests\Fixtures\ServiceBuilder'))); 217 | $configs[] = array('guzzle' => array('service_builder' => array('configuration_file' => '%kernel.root_dir%/config/alt-webservices.json'))); 218 | 219 | return $configs; 220 | } 221 | 222 | /** 223 | * @dataProvider logFormats 224 | */ 225 | public function testLogFormat($parameter, $format = null) 226 | { 227 | $container = $this->getContainer(array(array('log' => array('format' => $parameter)))); 228 | $this->assertEquals($format ?: $parameter, $container->getDefinition('misd_guzzle.log.monolog')->getArgument(1)); 229 | } 230 | 231 | public function logFormats() 232 | { 233 | return array( 234 | array('debug', MessageFormatter::DEBUG_FORMAT), 235 | array('short', MessageFormatter::SHORT_FORMAT), 236 | array('foo bar baz') 237 | ); 238 | } 239 | } 240 | --------------------------------------------------------------------------------