From 216b227b6b8217dc204149923718495db385a8f5 Mon Sep 17 00:00:00 2001 From: Alexander Janssen Date: Wed, 3 Jun 2015 14:52:22 +0200 Subject: [PATCH] Issue #30 --- .../CsaGuzzleExtension.php | 12 + .../Command/Guzzle/GuzzleClient.php | 45 ++++ .../Guzzle/Subscriber/ProcessResponse.php | 229 ++++++++++++++++++ .../ParamConverter/GuzzleConverter.php | 111 +++++++++ src/Resources/config/services.xml | 7 +- 5 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 src/GuzzleHttp/Command/Guzzle/GuzzleClient.php create mode 100644 src/GuzzleHttp/Command/Guzzle/Subscriber/ProcessResponse.php create mode 100644 src/Request/ParamConverter/GuzzleConverter.php diff --git a/src/DependencyInjection/CsaGuzzleExtension.php b/src/DependencyInjection/CsaGuzzleExtension.php index f44e19cf..adfb643b 100644 --- a/src/DependencyInjection/CsaGuzzleExtension.php +++ b/src/DependencyInjection/CsaGuzzleExtension.php @@ -115,7 +115,19 @@ private function processClientsConfiguration(array $config, ContainerBuilder $co 'service("csa_guzzle.description_factory").getDescription("%s")', $name ))); + $serviceDefinition->addArgument(['process' => false]); + $serviceDefinition->addMethodCall('setSerializer', [new Reference('serializer')]); + $serviceDefinition->addMethodCall('addProcess'); + $container->setDefinition(sprintf('csa_guzzle.service.%s', $name), $serviceDefinition); + $container->getDefinition('csa_guzzle.paramconverter.guzzle')->addMethodCall( + 'addService', + [ + sprintf('csa_guzzle.service.%s', $name), + new Reference(sprintf('csa_guzzle.service.%s', $name)) + ] + ); + } } } diff --git a/src/GuzzleHttp/Command/Guzzle/GuzzleClient.php b/src/GuzzleHttp/Command/Guzzle/GuzzleClient.php new file mode 100644 index 00000000..c55d9658 --- /dev/null +++ b/src/GuzzleHttp/Command/Guzzle/GuzzleClient.php @@ -0,0 +1,45 @@ +serializer = $serializer; + } + + + public function addProcess() + { + $this->getEmitter()->attach( + new ProcessResponse( + $this->getDescription(), + $this->serializer, + isset($config['response_locations']) + ? $config['response_locations'] + : [] + ) + ); + } + + +} \ No newline at end of file diff --git a/src/GuzzleHttp/Command/Guzzle/Subscriber/ProcessResponse.php b/src/GuzzleHttp/Command/Guzzle/Subscriber/ProcessResponse.php new file mode 100644 index 00000000..c6f178a1 --- /dev/null +++ b/src/GuzzleHttp/Command/Guzzle/Subscriber/ProcessResponse.php @@ -0,0 +1,229 @@ + new BodyLocation('body'), + 'header' => new HeaderLocation('header'), + 'reasonPhrase' => new ReasonPhraseLocation('reasonPhrase'), + 'statusCode' => new StatusCodeLocation('statusCode'), + 'xml' => new XmlLocation('xml'), + 'json' => new JsonLocation('json') + ]; + } + + $this->responseLocations = $responseLocations + $defaultResponseLocations; + $this->description = $description; + $this->serializer = $serializer; + } + + public function getEvents() + { + return ['process' => ['onProcess']]; + } + + public function onProcess(ProcessEvent $event) + { + // Only add a result object if no exception was encountered. + if ($event->getException()) { + return; + } + + $command = $event->getCommand(); + + // Do not overwrite a previous result + if ($event->getResult()) { + return; + } + + $operation = $this->description->getOperation($command->getName()); + + // Add a default Model as the result if no matching schema was found. + if (!($modelName = $operation->getResponseModel())) { + $event->setResult([]); + return; + } + + $model = $operation->getServiceDescription()->getModel($modelName); + if (!$model) { + throw new \RuntimeException("Unknown model: {$modelName}"); + } + + $event->setResult($this->visit($model, $event)); + } + + protected function visit(Parameter $model, ProcessEvent $event) + { + $result = []; + $context = ['client' => $event->getClient(), 'visitors' => []]; + $command = $event->getCommand(); + $response = $event->getResponse(); + + if ($model->getType() == 'object') { + $this->visitOuterObject($model, $result, $command, $response, $context); + } elseif ($model->getType() == 'array') { + $this->visitOuterArray($model, $result, $command, $response, $context); + } elseif ($model->getType() == 'serializer') { + $this->visitOuterSerializer($model, $result, $command, $response, $context); + } else { + throw new \InvalidArgumentException('Invalid response model: ' . $model->getType()); + } + + // Call the after() method of each found visitor + foreach ($context['visitors'] as $visitor) { + $visitor->after($command, $response, $model, $result, $context); + } + + return $result; + } + + private function triggerBeforeVisitor( + $location, + Parameter $model, + array &$result, + CommandInterface $command, + ResponseInterface $response, + array &$context + ) { + if (!isset($this->responseLocations[$location])) { + throw new \RuntimeException("Unknown location: $location"); + } + + $context['visitors'][$location] = $this->responseLocations[$location]; + + $this->responseLocations[$location]->before( + $command, + $response, + $model, + $result, + $context + ); + } + + private function visitOuterObject( + Parameter $model, + array &$result, + CommandInterface $command, + ResponseInterface $response, + array &$context + ) { + $parentLocation = $model->getLocation(); + + // If top-level additionalProperties is a schema, then visit it + $additional = $model->getAdditionalProperties(); + if ($additional instanceof Parameter) { + // Use the model location if none set on additionalProperties. + $location = $additional->getLocation() ?: $parentLocation; + $this->triggerBeforeVisitor( + $location, $model, $result, $command, $response, $context + ); + } + + // Use 'location' from all individual defined properties, but fall back + // to the model location if no per-property location is set. Collect + // the properties that need to be visited into an array. + $visitProperties = []; + foreach ($model->getProperties() as $schema) { + $location = $schema->getLocation() ?: $parentLocation; + if ($location) { + $visitProperties[] = [$location, $schema]; + // Trigger the before method on each unique visitor location + if (!isset($context['visitors'][$location])) { + $this->triggerBeforeVisitor( + $location, $model, $result, $command, $response, $context + ); + } + } + } + + // Actually visit each response element + foreach ($visitProperties as $prop) { + $this->responseLocations[$prop[0]]->visit( + $command, $response, $prop[1], $result, $context + ); + } + } + + private function visitOuterArray( + Parameter $model, + array &$result, + CommandInterface $command, + ResponseInterface $response, + array &$context + ) { + // Use 'location' defined on the top of the model + if (!($location = $model->getLocation())) { + return; + } + + if (!isset($foundVisitors[$location])) { + $this->triggerBeforeVisitor( + $location, $model, $result, $command, $response, $context + ); + } + + // Visit each item in the response + $this->responseLocations[$location]->visit( + $command, $response, $model, $result, $context + ); + } + + private function visitOuterSerializer( + Parameter $model, + array &$result, + CommandInterface $command, + ResponseInterface $response, + array &$context + ) { + $result = $this->serializer->deserialize($response->getBody(), $model->class, 'json'); + } +} \ No newline at end of file diff --git a/src/Request/ParamConverter/GuzzleConverter.php b/src/Request/ParamConverter/GuzzleConverter.php new file mode 100644 index 00000000..bc392467 --- /dev/null +++ b/src/Request/ParamConverter/GuzzleConverter.php @@ -0,0 +1,111 @@ +services; + } + + /** + * @param array $services + */ + public function setServices($services) + { + $this->services = $services; + } + + public function addService($name, $service) + { + $this->services[$name] = $service; + } + + /** + * Stores the object in the request. + * + * @param Request $request The request + * @param ParamConverter $configuration Contains the name, class and options of the object + * + * @return bool True if the object has been successfully set, else false + */ + public function apply(Request $request, ParamConverter $configuration) + { + $client = $this->findService($configuration); + $description = $client->getDescription(); + /** @var Description $description */ + if (isset($configuration->getOptions()['operation'])) { + $function = $configuration->getOptions()['operation']; + } else { + foreach ($description->getOperations() as $name => $searchOperation) { + if (isset($searchOperation['responseModel']) && $searchOperation['responseModel'] == $configuration->getClass()) { + $function = $name; + break; + } + } + } + + $operation = $description->getOperation($function); + if (isset($operation)) { + $parameters = []; + foreach ($operation->getParams() as $param) { + $parameters[$param->getName()] = $request->get($param->getName()); + } + try { + $request->attributes->set($configuration->getName(), $client->$function($parameters)); + } catch (\Exception $e) { + } + } + + } + + /** + * Checks if the object is supported. + * + * @param ParamConverter $configuration Should be an instance of ParamConverter + * + * @return bool True if the object is supported, else false + */ + public function supports(ParamConverter $configuration) + { + return ($this->findService($configuration) !== null); + } + + public function findService($configuration) + { + + foreach ($this->services as $service) { + $description = $service->getDescription(); + /** @var Description $description */ + + foreach ($description->getModels() as $model) { + if ($configuration->getClass() == $model->class) { + return $service; + } + } + } + } + +} \ No newline at end of file diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index fd728f5f..ef4f77c9 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -27,7 +27,12 @@ - + + + + + +