From 0f57d6ac226cbec82bb7e977ac6a5637129bf26c Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Sat, 29 Sep 2012 23:53:43 -0700 Subject: [PATCH] [Service] Adding Model classes that can be used to make it easier to consume the models generated by service description powered OperationCommands. --- .../Service/Command/AbstractCommand.php | 2 + .../Command/OperationResponseParser.php | 6 ++ src/Guzzle/Service/Resource/Model.php | 72 +++++++++++++++++++ .../Service/Command/OperationCommandTest.php | 5 +- .../Command/OperationResponseParserTest.php | 16 +++++ .../Tests/Service/Resource/ModelTest.php | 41 +++++++++++ 6 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/Guzzle/Service/Resource/Model.php create mode 100644 tests/Guzzle/Tests/Service/Resource/ModelTest.php diff --git a/src/Guzzle/Service/Command/AbstractCommand.php b/src/Guzzle/Service/Command/AbstractCommand.php index 3775a89a..b35c0203 100644 --- a/src/Guzzle/Service/Command/AbstractCommand.php +++ b/src/Guzzle/Service/Command/AbstractCommand.php @@ -29,6 +29,8 @@ abstract class AbstractCommand extends Collection implements CommandInterface const DISABLE_VALIDATION = 'command.disable_validation'; // Option used to override how a command result will be formatted const RESPONSE_PROCESSING = 'command.response_processing'; + // Options to specify that response models should be a raw array rather than a model object + const RESPONSE_MODEL_ARRAY = 'command.response_model_array'; // Different response types that commands can use const TYPE_RAW = 'raw'; const TYPE_NATIVE = 'native'; diff --git a/src/Guzzle/Service/Command/OperationResponseParser.php b/src/Guzzle/Service/Command/OperationResponseParser.php index 513650a1..9160efac 100644 --- a/src/Guzzle/Service/Command/OperationResponseParser.php +++ b/src/Guzzle/Service/Command/OperationResponseParser.php @@ -10,6 +10,7 @@ use Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor; use Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor; use Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor; use Guzzle\Service\Command\LocationVisitor\Response\ResponseVisitorInterface; +use Guzzle\Service\Resource\Model; /** * Response parser that attempts to marshal responses into an associative array based on models in a service description @@ -112,6 +113,11 @@ class OperationResponseParser extends DefaultResponseParser $visitor->after($command); } + // Use a model object if it is not disabled on the command + if (!$command->get(AbstractCommand::RESPONSE_MODEL_ARRAY)) { + $result = new Model($result, $model); + } + return $result; } } diff --git a/src/Guzzle/Service/Resource/Model.php b/src/Guzzle/Service/Resource/Model.php new file mode 100644 index 00000000..617665c8 --- /dev/null +++ b/src/Guzzle/Service/Resource/Model.php @@ -0,0 +1,72 @@ +data = $data; + $this->structure = $structure; + } + + /** + * Get the structure of the model + * + * @return Parameter + */ + public function getStructure() + { + return $this->structure; + } + + /** + * Gets a value from the model using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays) + * + * @param string $path Path to traverse and retrieve a value from + * @param string $separator Character used to add depth to the search + * + * @return mixed|null + */ + public function getPath($path, $separator = '/') + { + $parts = explode($separator, $path); + $data = &$this->data; + + // Using an iterative approach rather than recursion for speed + while ($part = array_shift($parts)) { + // Return null if this path doesn't exist or if there's more depth and the value is not an array + if (!isset($data[$part]) || ($parts && !is_array($data[$part]))) { + return null; + } + $data = &$data[$part]; + } + + return $data; + } + + /** + * Convert the model to an array + * + * @return array + */ + public function toArray() + { + return $this->data; + } +} diff --git a/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php b/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php index dd786b27..aed90efb 100644 --- a/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php +++ b/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php @@ -9,6 +9,7 @@ use Guzzle\Service\Command\OperationCommand; use Guzzle\Service\Description\Operation; use Guzzle\Service\Description\ServiceDescription; use Guzzle\Service\Command\DefaultRequestSerializer; +use Guzzle\Service\Resource\Model; /** * @covers Guzzle\Service\Command\OperationCommand @@ -66,9 +67,9 @@ class OperationCommandTest extends \Guzzle\Tests\GuzzleTestCase $request->setResponse(new Response(200, array( 'Content-Type' => 'application/xml' ), 'Bar'), true); - $this->assertEquals(array( + $this->assertEquals(new Model(array( 'Baz' => 'Bar' - ), $op->execute()); + ), $description->getModel('bar')), $op->execute()); } public function testAllowsRawResponses() diff --git a/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php b/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php index f905807a..3014c4c4 100644 --- a/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php +++ b/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php @@ -4,13 +4,16 @@ namespace Guzzle\Tests\Service\Command; use Guzzle\Http\Message\Response; use Guzzle\Service\Client; +use Guzzle\Service\Command\AbstractCommand; use Guzzle\Service\Command\OperationResponseParser; use Guzzle\Service\Command\OperationCommand; use Guzzle\Service\Description\Operation; +use Guzzle\Service\Description\Parameter; use Guzzle\Service\Description\ServiceDescription; use Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor; use Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor; use Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor; +use Guzzle\Service\Resource\Model; /** * @covers Guzzle\Service\Command\OperationResponseParser @@ -42,7 +45,20 @@ class OperationResponseParserTest extends \Guzzle\Tests\GuzzleTestCase $op = new OperationCommand(array(), $this->getDescription()->getOperation('test')); $op->setResponseParser($parser)->setClient(new Client()); $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/xml'), 'C'), true); + $this->assertInstanceOf('Guzzle\Service\Resource\Model', $op->execute()); + $this->assertEquals('C', $op->getResult()->get('B')); + } + + public function testUsesRawArraysWhenInstructed() + { + $parser = new OperationResponseParser(); + $op = new OperationCommand(array(), $this->getDescription()->getOperation('test')); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/xml'), 'C'), true); + $op->set(AbstractCommand::RESPONSE_MODEL_ARRAY, true); $this->assertInternalType('array', $op->execute()); + $result = $op->getResult(); + $this->assertEquals('C', $result['B']); } public function testVisitsLocations() diff --git a/tests/Guzzle/Tests/Service/Resource/ModelTest.php b/tests/Guzzle/Tests/Service/Resource/ModelTest.php new file mode 100644 index 00000000..50530982 --- /dev/null +++ b/tests/Guzzle/Tests/Service/Resource/ModelTest.php @@ -0,0 +1,41 @@ + 'object')); + $model = new Model(array('foo' => 'bar'), $param); + $this->assertSame($param, $model->getStructure()); + $this->assertEquals('bar', $model->get('foo')); + $this->assertEquals('bar', $model['foo']); + } + + public function testRetrievesNestedKeysUsingPath() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar' + ) + ) + ); + $param = new Parameter(array('type' => 'object')); + $model = new Model($data, $param); + $this->assertSame($param, $model->getStructure()); + $this->assertEquals('bar', $model->getPath('foo')); + $this->assertEquals('jar', $model->getPath('baz/mesa/jar')); + $this->assertNull($model->getPath('wewewf')); + $this->assertNull($model->getPath('baz/mesa/jar/jar')); + $this->assertSame($data, $model->toArray()); + } +}