1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-02-25 02:22:57 +01:00

Adding name attribute to parameters that are a child of an array. Adding plugins to service builders. Adding the ability for the XML response parser to map collections into flattened arrays.

This commit is contained in:
Michael Dowling 2012-09-26 22:18:54 -07:00
parent dc91935cf6
commit b961ecbfa4
9 changed files with 170 additions and 135 deletions

View File

@ -6,6 +6,7 @@ use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Http\ClientInterface;
use Guzzle\Service\Exception\ServiceBuilderException;
use Guzzle\Service\Exception\ServiceNotFoundException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Service builder to generate service builders and service clients from configuration settings
@ -27,6 +28,11 @@ class ServiceBuilder extends AbstractHasDispatcher implements ServiceBuilderInte
*/
protected static $cachedFactory;
/**
* @var array Plugins to attach to each client created by the service builder
*/
protected $plugins = array();
/**
* Create a new ServiceBuilder using configuration data sourced from an
* array, .json|.js file, SimpleXMLElement, or .xml file.
@ -90,6 +96,20 @@ class ServiceBuilder extends AbstractHasDispatcher implements ServiceBuilderInte
return json_encode($this->builderConfig);
}
/**
* Attach a plugin to every client created by the builder
*
* @param EventSubscriberInterface $plugin Plugin to attach to each client
*
* @return self
*/
public function addGlobalPlugin(EventSubscriberInterface $plugin)
{
$this->plugins[] = $plugin;
return $this;
}
/**
* {@inheritdoc}
*/
@ -117,6 +137,10 @@ class ServiceBuilder extends AbstractHasDispatcher implements ServiceBuilderInte
$this->clients[$name] = $client;
}
foreach ($this->plugins as $plugin) {
$client->addSubscriber($plugin);
}
// Dispatch an event letting listeners know a client was created
$this->dispatch('service_builder.create_client', array(
'client' => $client

View File

@ -99,13 +99,13 @@ class XmlVisitor extends AbstractRequestVisitor
protected function addXml(\SimpleXMLElement $xml, Parameter $param, $value)
{
// Determine the name of the element
$node = $param->getRename() ?: $param->getName();
$node = $param->getKey();
// Check if this property has a particular namespace
$namespace = $param->getData('namespace') ?: null;
if ($param->getType() == 'array') {
if ($items = $param->getItems()) {
$name = $items->getRename();
$name = $items->getKey();
foreach ($value as $v) {
if ($items->getType() == 'object' || $items->getType() == 'array') {
$child = $xml->addChild($name, null, $namespace);

View File

@ -35,29 +35,50 @@ class XmlVisitor extends AbstractResponseVisitor
*/
protected function recursiveProcess(Parameter $param, &$value)
{
if ($value !== null) {
$type = $param->getType();
if (is_array($value)) {
if ($type == 'array') {
// Convert the node if it was meant to be an array
if (!isset($value[0])) {
$type = $param->getType();
if (is_array($value)) {
if ($type == 'array') {
// Convert the node if it was meant to be an array
if (!isset($value[0])) {
// Collections fo nodes are sometimes wrapped in an additional array. For example:
// <Items><Item><a>1</a></Item><Item><a>2</a></Item></Items> should become:
// array('Items' => array(array('a' => 1), array('a' => 2))
// Some nodes are not wrapped. For example: <Foo><a>1</a></Foo><Foo><a>2</a></Foo>
// should become array('Foo' => array(array('a' => 1), array('a' => 2))
if ($param->getItems() && isset($value[$param->getItems()->getName()])) {
// Account for the case of a collection wrapping wrapped nodes: Items => Item[]
$value = $value[$param->getItems()->getName()];
// If the wrapped node only had one value, then make it an array of nodes
if (!isset($value[0])) {
$value = array($value);
}
} elseif (!empty($value)) {
// Account for repeated nodes that must be an array: Foo => Baz, Foo => Baz, but only if the
// value is set and not empty
$value = array($value);
}
foreach ($value as &$item) {
$this->recursiveProcess($param->getItems(), $item);
}
} elseif ($type == 'object' && !isset($value[0])) {
// On the above line, we ensure that the array is associative and not numerically indexed
if ($properties = $param->getProperties()) {
foreach ($properties as $property) {
$name = $property->getName();
if (isset($value[$name])) {
$this->recursiveProcess($property, $value[$name]);
}
}
foreach ($value as &$item) {
$this->recursiveProcess($param->getItems(), $item);
}
} elseif ($type == 'object' && !isset($value[0])) {
// On the above line, we ensure that the array is associative and not numerically indexed
if ($properties = $param->getProperties()) {
foreach ($properties as $property) {
$name = $property->getName();
if (isset($value[$name])) {
$this->recursiveProcess($property, $value[$name]);
}
}
}
}
}
if ($value !== null) {
$value = $param->filter($value);
}
}

View File

@ -131,10 +131,15 @@ class Parameter
public function toArray()
{
$result = array();
foreach (array(
'required', 'description', 'static', 'type', 'instanceOf', 'location', 'rename', 'pattern', 'minimum',
'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum', 'filters'
) as $c) {
$checks = array('required', 'description', 'static', 'type', 'instanceOf', 'location', 'rename', 'pattern',
'minimum', 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum', 'filters');
// Anything that is in the `Items` attribute of an array *must* include it's name if available
if ($this->parent instanceof self && $this->parent->getType() == 'array' && isset($this->name)) {
$result['name'] = $this->name;
}
foreach ($checks as $c) {
if ($value = $this->{$c}) {
$result[$c] = $value;
}

View File

@ -1,49 +0,0 @@
<?php
namespace Guzzle\Service\Plugin;
use Guzzle\Common\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Service builder plugin used to add plugins to all clients created by a {@see Guzzle\Service\Builder\ServiceBuilder}
*
* @author Gordon Franke <info@nevalon.de>
*/
class PluginCollectionPlugin implements EventSubscriberInterface
{
/**
* @var $plugins array plugins to add
*/
private $plugins = array();
/**
* @param array $plugins plugins to add
*/
public function __construct(array $plugins)
{
$this->plugins = $plugins;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
'service_builder.create_client' => 'onCreateClient'
);
}
/**
* Adds plugins to clients as they are created by the service builder
*
* @param Event $event Event emitted
*/
public function onCreateClient(Event $event)
{
foreach ($this->plugins as $plugin) {
$event['client']->addSubscriber($plugin);
}
}
}

View File

@ -2,7 +2,7 @@
namespace Guzzle\Tests\Service;
use Guzzle\Cache\DoctrineCacheAdapter;
use Guzzle\Plugin\History\HistoryPlugin;
use Guzzle\Service\Builder\ServiceBuilder;
use Guzzle\Service\Client;
use Guzzle\Service\Exception\ServiceNotFoundException;
@ -15,7 +15,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
protected $arrayData = array(
'michael.mock' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'username' => 'michael',
'password' => 'testing123',
@ -23,7 +23,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
),
),
'billy.mock' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'username' => 'billy',
'password' => 'passw0rd',
@ -42,12 +42,12 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
'cache.adapter' => array(
'class' => 'Guzzle\Cache\CacheAdapterFactory',
'params' => array(
'cache.adapter' => 'Guzzle.Cache.DoctrineCacheAdapter',
'cache.provider' => 'Doctrine.Common.Cache.ArrayCache'
'cache.adapter' => 'Guzzle\Cache\DoctrineCacheAdapter',
'cache.provider' => 'Doctrine\Common\Cache\ArrayCache'
)
),
'service_uses_cache' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'cache' => '{cache.adapter}',
'username' => 'foo',
@ -68,7 +68,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$builder = ServiceBuilder::factory($this->arrayData);
$c = $builder->get('michael.mock');
$this->assertInstanceOf('Guzzle\\Tests\\Service\\Mock\\MockClient', $c);
$this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c);
}
/**
@ -84,13 +84,13 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$builder = ServiceBuilder::factory($this->arrayData);
$client = $builder->get('michael.mock');
$this->assertInstanceOf('Guzzle\\Tests\\Service\\Mock\\MockClient', $client);
$this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client);
$this->assertEquals('http://127.0.0.1:8124/v1/michael', $client->getBaseUrl());
$this->assertEquals($client, $builder->get('michael.mock'));
// Get another client but throw this one away
$client2 = $builder->get('billy.mock', true);
$this->assertInstanceOf('Guzzle\\Tests\\Service\\Mock\\MockClient', $client2);
$this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client2);
$this->assertEquals('http://127.0.0.1:8124/v1/billy', $client2->getBaseUrl());
// Make sure the original client is still there and set
@ -110,7 +110,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$s = new ServiceBuilder(array(
'michael.mock' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'base_url' => 'http://www.test.com/',
'subdomain' => 'michael',
@ -129,7 +129,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$s = new ServiceBuilder(array(
'michael.mock' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'base_url' => 'http://www.test.com/',
'subdomain' => 'michael',
@ -141,7 +141,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
));
$c = $s->get('michael.mock');
$this->assertInstanceOf('Guzzle\\Tests\\Service\\Mock\\MockClient', $c);
$this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c);
}
public function testUsedAsArray()
@ -149,13 +149,13 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
$b = ServiceBuilder::factory($this->arrayData);
$this->assertTrue($b->offsetExists('michael.mock'));
$this->assertFalse($b->offsetExists('not_there'));
$this->assertInstanceOf('Guzzle\\Service\\Client', $b['michael.mock']);
$this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']);
unset($b['michael.mock']);
$this->assertFalse($b->offsetExists('michael.mock'));
$b['michael.mock'] = new Client('http://www.test.com/');
$this->assertInstanceOf('Guzzle\\Service\\Client', $b['michael.mock']);
$this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']);
}
public function testFactoryCanCreateFromJson()
@ -188,7 +188,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
{
$builder = ServiceBuilder::factory(array(
'a' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'other_client' => '{{ b }}',
'username' => 'x',
@ -197,7 +197,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
)
),
'b' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'username' => '1',
'password' => '2',
@ -220,7 +220,7 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
// Create a test service builder
$builder = ServiceBuilder::factory(array(
'a' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'class' => 'Guzzle\Tests\Service\Mock\MockClient',
'params' => array(
'username' => 'test',
'password' => '123',
@ -281,4 +281,12 @@ class ServiceBuilderTest extends \Guzzle\Tests\GuzzleTestCase
$builder['service_uses_cache']->getConfig('cache')
);
}
public function testAddsGlobalPlugins()
{
$b = new ServiceBuilder($this->arrayData);
$b->addGlobalPlugin(new HistoryPlugin());
$s = $b->get('michael.mock');
$this->assertTrue($s->getEventDispatcher()->hasListeners('request.complete'));
}
}

View File

@ -21,7 +21,7 @@ class XmlVisitorTest extends AbstractResponseVisitorTest
$this->assertEquals('test', $value['foo']);
}
public function testEnsuresArraysAreInCorrectLocations()
public function testEnsuresRepeatedArraysAreInCorrectLocations()
{
$visitor = new Visitor();
$param = new Parameter(array(
@ -33,13 +33,16 @@ class XmlVisitorTest extends AbstractResponseVisitorTest
'type' => 'object',
'properties' => array(
'Bar' => array('type' => 'string'),
'Baz' => array('type' => 'string')
'Baz' => array('type' => 'string'),
'Bam' => array('type' => 'string')
)
)
));
$xml = new \SimpleXMLElement('<Test><Foo><Bar>1</Bar><Baz>2</Baz></Foo></Test>');
$value = json_decode(json_encode($xml), true);
// Set a null value to ensure it is ignored
$value['foo'][0]['Bam'] = null;
$visitor->visit($this->command, $this->response, $param, $value);
$this->assertEquals(array(
'foo' => array(
@ -50,4 +53,50 @@ class XmlVisitorTest extends AbstractResponseVisitorTest
)
), $value);
}
public function xmlDataProvider()
{
$param = new Parameter(array(
'location' => 'xml',
'name' => 'Items',
'type' => 'array',
'items' => array(
'type' => 'object',
'name' => 'Item',
'properties' => array(
'Bar' => array('type' => 'string'),
'Baz' => array('type' => 'string')
)
)
));
return array(
array($param, '<Test><Items><Item><Bar>1</Bar></Item><Item><Bar>2</Bar></Item></Items></Test>', array(
'Items' => array(
array('Bar' => 1),
array('Bar' => 2)
)
)),
array($param, '<Test><Items><Item><Bar>1</Bar></Item></Items></Test>', array(
'Items' => array(
array('Bar' => 1)
)
)),
array($param, '<Test><Items /></Test>', array(
'Items' => array()
))
);
}
/**
* @dataProvider xmlDataProvider
*/
public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $result)
{
$visitor = new Visitor();
$xml = new \SimpleXMLElement($xml);
$value = json_decode(json_encode($xml), true);
$visitor->visit($this->command, $this->response, $param, $value);
$this->assertEquals($result, $value);
}
}

View File

@ -324,4 +324,25 @@ class ParameterTest extends \Guzzle\Tests\GuzzleTestCase
$p->setRename(null);
$this->assertEquals('foo', $p->getKey());
}
public function testIncludesNameInToArrayWhenItemsAttriubuteHasName()
{
$p = new Parameter(array(
'type' => 'array',
'name' => 'Abc',
'items' => array(
'name' => 'Foo',
'type' => 'object'
)
));
$result = $p->toArray();
$this->assertEquals(array(
'type' => 'array',
'items' => array(
'name' => 'Foo',
'type' => 'object',
'additionalProperties' => true
)
), $result);
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace Guzzle\Tests\Service;
use Guzzle\Service\Builder\ServiceBuilder;
use Guzzle\Service\Plugin\PluginCollectionPlugin;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PluginCollectionPluginTest extends \Guzzle\Tests\GuzzleTestCase
{
/**
* @covers Guzzle\Service\Plugin\PluginCollectionPlugin
*/
public function testPluginPassPluginsThroughToClients()
{
$s = new ServiceBuilder(array(
'michael.mock' => array(
'class' => 'Guzzle\\Tests\\Service\\Mock\\MockClient',
'params' => array(
'base_url' => 'http://www.test.com/',
'subdomain' => 'michael',
'password' => 'test',
'username' => 'michael',
)
)
));
$plugin = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
$plugin::staticExpects($this->any())
->method('getSubscribedEvents')
->will($this->returnValue(array('client.create_request' => 'onRequestCreate')));
$s->addSubscriber(new PluginCollectionPlugin(array($plugin)));
$c = $s->get('michael.mock');
$this->assertTrue($c->getEventDispatcher()->hasListeners('client.create_request'));
$listeners = $c->getEventDispatcher()->getListeners('client.create_request');
$this->assertSame($plugin, $listeners[0][0]);
$this->assertEquals('onRequestCreate', $listeners[0][1]);
}
}