CHANGELOG.md000064400000004443144760114660006373 0ustar00# CHANGELOG ## 1.0.0 - 2016-11-24 * Add badges to README.md * Switch README from .rst to .md format * Update dependencies * Add command to handler call to provide support for GuzzleServices ## 0.9.0 - 2016-01-30 * Updated to use Guzzle 6 and PSR-7. * Event system has been replaced with a middleware system * Middleware at the command layer work the same as middleware from the HTTP layer, but work with `Command` and `Result` objects instead of `Request` and `Response` objects * The command middleware is in a separate `HandlerStack` instance than the HTTP middleware. * `Result` objects are the result of executing a `Command` and are used to hold the parsed response data. * Asynchronous code now uses the `guzzlehttp/promises` package instead of `guzzlehttp/ringphp`, which means that asynchronous results are implemented as Promises/A+ compliant `Promise` objects, instead of futures. * The existing `Subscriber`s were removed. * The `ServiceClientInterface` and `ServiceClient` class now provide the basic foundation of a web service client. ## 0.8.0 - 2015-02-02 * Removed `setConfig` from `ServiceClientInterface`. * Added `initTransaction` to `ServiceClientInterface`. ## 0.7.1 - 2015-01-14 * Fixed and issue where intercepting commands encapsulated by a CommandToRequestIterator could lead to deep recursion. These commands are now skipped and the iterator moves to the next element using a `goto` statement. ## 0.7.0 - 2014-10-12 * Updated to use Guzzle 5, and added support for asynchronous results. * Renamed `prepare` event to `prepared`. * Added `init` event. ## 0.6.0 - 2014-08-08 * Added a Debug subscriber that can be used to trace through the lifecycle of a command and how it is modified in each event. ## 0.5.0 - 2014-08-01 * Rewrote event system so that all exceptions encountered during the transfer of a command are emitted to the "error" event. * No longer wrapping exceptions thrown during the execution of a command. * Added the ability to get a CommandTransaction from events and updating classes to use a CommandTransaction rather than many constructor arguments. * Fixed an issue with sending many commands in parallel * Added `batch()` to ServiceClientInterface for sending commands in batches * Added subscriber to easily mock commands results LICENSE000064400000002306144760114660005563 0ustar00The MIT License (MIT) Copyright (c) 2014 Michael Dowling Copyright (c) 2014 Graham Campbell Copyright (c) 2014 Jeremy Lindblom 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. Makefile000064400000000042144760114660006211 0ustar00test: vendor/bin/phpunit $(TEST) README.md000064400000011233144760114660006034 0ustar00# Guzzle Commands This library uses Guzzle (``guzzlehttp/guzzle``, version 7.x) and provides the foundations to create fully-featured web service clients by abstracting Guzzle HTTP **requests** and **responses** into higher-level **commands** and **results**. A **middleware** system, analogous to — but separate from — the one in the HTTP layer may be used to customize client behavior when preparing commands into requests and processing responses into results. ### Commands Key-value pair objects representing an operation of a web service. Commands have a name and a set of parameters. ### Results Key-value pair objects representing the processed result of executing an operation of a web service. ## Installing This project can be installed using Composer: ``composer require guzzlehttp/command`` For **Guzzle 5**, use ``composer require guzzlehttp/command:0.8.*``. The source code for the Guzzle 5 version is available on the `0.8 branch `_. **Note:** If Composer is not installed [globally](https://getcomposer.org/doc/00-intro.md#globally) then you may need to run the preceding Composer commands using ``php composer.phar`` (where ``composer.phar`` is the path to your copy of Composer), instead of just ``composer``. ## Service Clients Service Clients are web service clients that implement the ``GuzzleHttp\Command\ServiceClientInterface`` and use an underlying Guzzle HTTP client (``GuzzleHttp\Client``) to communicate with the service. Service clients create and execute **commands** (``GuzzleHttp\Command\CommandInterface``), which encapsulate operations within the web service, including the operation name and parameters. This library provides a generic implementation of a service client: the ``GuzzleHttp\Command\ServiceClient`` class. ## Instantiating a Service Client @TODO Add documentation * ``ServiceClient``'s constructor * Transformer functions (``$commandToRequestTransformer`` and ``$responseToResultTransformer``) * The ``HandlerStack`` ## Executing Commands Service clients create command objects using the ``getCommand()`` method. ```php $commandName = 'foo'; $arguments = ['baz' => 'bar']; $command = $client->getCommand($commandName, $arguments); ``` After creating a command, you may execute the command using the ``execute()`` method of the client. ```php $result = $client->execute($command); ``` The result of executing a command will be a ``GuzzleHttp\Command\ResultInterface`` object. Result objects are ``ArrayAccess``-ible and contain the data parsed from HTTP response. Service clients have magic methods that act as shortcuts to executing commands by name without having to create the ``Command`` object in a separate step before executing it. ```php $result = $client->foo(['baz' => 'bar']); ``` ## Asynchronous Commands @TODO Add documentation * ``-Async`` suffix for client methods * Promises ```php // Create and execute an asynchronous command. $command = $command = $client->getCommand('foo', ['baz' => 'bar']); $promise = $client->executeAsync($command); // Use asynchronous commands with magic methods. $promise = $client->fooAsync(['baz' => 'bar']); ``` @TODO Add documentation * ``wait()``-ing on promises. ```php $result = $promise->wait(); echo $result['fizz']; //> 'buzz' ``` ## Concurrent Requests @TODO Add documentation * ``executeAll()`` * ``executeAllAsync()``. * Options (``fulfilled``, ``rejected``, ``concurrency``) ## Middleware: Extending the Client Middleware can be added to the service client or underlying HTTP client to implement additional behavior and customize the ``Command``-to-``Result`` and ``Request``-to-``Response`` lifecycles, respectively. ## Security If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/command/security/policy) for more information. ## License Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. ## For Enterprise Available as part of the Tidelift Subscription The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-command?utm_source=packagist-guzzlehttp-command&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) composer.json000064400000002476144760114660007310 0ustar00{ "name": "guzzlehttp/command", "description": "Provides the foundation for building command-based web service clients", "license": "MIT", "authors": [ { "name": "Graham Campbell", "email": "hello@gjcampbell.co.uk", "homepage": "https://github.com/GrahamCampbell" }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, { "name": "Jeremy Lindblom", "email": "jeremeamia@gmail.com", "homepage": "https://github.com/jeremeamia" }, { "name": "Tobias Nyholm", "email": "tobias.nyholm@gmail.com", "homepage": "https://github.com/Nyholm" } ], "require": { "php": "^7.2.5 || ^8.0", "guzzlehttp/guzzle": "^7.3", "guzzlehttp/promises": "^1.3", "guzzlehttp/psr7": "^1.7 || ^2.0" }, "require-dev": { "phpunit/phpunit": "^8.5.19" }, "autoload": { "psr-4": { "GuzzleHttp\\Command\\": "src/" } }, "extra": { "branch-alias": { "dev-master": "1.2-dev" } }, "config": { "preferred-install": "dist", "sort-packages": true } } phpunit.xml.dist000064400000000725144760114660007734 0ustar00 tests src src/Command.php000064400000002175144760114660007440 0ustar00name = $name; $this->data = $args; $this->handlerStack = $handlerStack; } public function getHandlerStack() { return $this->handlerStack; } public function getName() { return $this->name; } public function hasParam($name) { return array_key_exists($name, $this->data); } public function __clone() { if ($this->handlerStack) { $this->handlerStack = clone $this->handlerStack; } } } src/CommandInterface.php000064400000001742144760114660011260 0ustar00getCommand()) { return $prev; } // If the exception is a RequestException, get the Request and Response. $request = $response = null; if ($prev instanceof RequestException) { $request = $prev->getRequest(); $response = $prev->getResponse(); } // Throw a more specific exception for 4XX or 5XX responses. $class = self::class; $statusCode = $response ? $response->getStatusCode() : 0; if ($statusCode >= 400 && $statusCode < 500) { $class = CommandClientException::class; } elseif ($statusCode >= 500 && $statusCode < 600) { $class = CommandServerException::class; } // Prepare the message. $message = 'There was an error executing the ' . $command->getName() . ' command: ' . $prev->getMessage(); // Create the exception. return new $class($message, $command, $prev, $request, $response); } /** * @param string $message Exception message * @param CommandInterface $command * @param \Exception $previous Previous exception (if any) * @param RequestInterface $request * @param ResponseInterface $response */ public function __construct( $message, CommandInterface $command, \Exception $previous = null, RequestInterface $request = null, ResponseInterface $response = null ) { $this->command = $command; $this->request = $request; $this->response = $response; parent::__construct($message, 0, $previous); } /** * Gets the command that failed. * * @return CommandInterface */ public function getCommand() { return $this->command; } /** * Gets the request that caused the exception * * @return RequestInterface|null */ public function getRequest() { return $this->request; } /** * Gets the associated response * * @return ResponseInterface|null */ public function getResponse() { return $this->response; } } src/Exception/CommandServerException.php000064400000000275144760114660014443 0ustar00data; } public function offsetExists($offset) { return array_key_exists($offset, $this->data); } public function offsetGet($offset) { return isset($this->data[$offset]) ? $this->data[$offset] : null; } public function offsetSet($offset, $value) { $this->data[$offset] = $value; } public function offsetUnset($offset) { unset($this->data[$offset]); } public function count() { return count($this->data); } public function getIterator() { return new \ArrayIterator($this->data); } public function toArray() { return $this->data; } } src/Result.php000064400000000430144760114660007330 0ustar00data = $data; } } src/ResultInterface.php000064400000000335144760114660011155 0ustar00httpClient = $httpClient; $this->commandToRequestTransformer = $commandToRequestTransformer; $this->responseToResultTransformer = $responseToResultTransformer; $this->handlerStack = $commandHandlerStack ?: new HandlerStack(); $this->handlerStack->setHandler($this->createCommandHandler()); } public function getHttpClient() { return $this->httpClient; } public function getHandlerStack() { return $this->handlerStack; } public function getCommand($name, array $params = []) { return new Command($name, $params, clone $this->handlerStack); } public function execute(CommandInterface $command) { return $this->executeAsync($command)->wait(); } public function executeAsync(CommandInterface $command) { $stack = $command->getHandlerStack() ?: $this->handlerStack; $handler = $stack->resolve(); return $handler($command); } public function executeAll($commands, array $options = []) { // Modify provided callbacks to track results. $results = []; $options['fulfilled'] = function ($v, $k) use (&$results, $options) { if (isset($options['fulfilled'])) { $options['fulfilled']($v, $k); } $results[$k] = $v; }; $options['rejected'] = function ($v, $k) use (&$results, $options) { if (isset($options['rejected'])) { $options['rejected']($v, $k); } $results[$k] = $v; }; // Execute multiple commands synchronously, then sort and return the results. return $this->executeAllAsync($commands, $options) ->then(function () use (&$results) { ksort($results); return $results; }) ->wait(); } public function executeAllAsync($commands, array $options = []) { // Apply default concurrency. if (!isset($options['concurrency'])) { $options['concurrency'] = 25; } // Convert the iterator of commands to a generator of promises. $commands = Promise\iter_for($commands); $promises = function () use ($commands) { foreach ($commands as $key => $command) { if (!$command instanceof CommandInterface) { throw new \InvalidArgumentException('The iterator must ' . 'yield instances of ' . CommandInterface::class); } yield $key => $this->executeAsync($command); } }; // Execute the commands using a pool. return (new Promise\EachPromise($promises(), $options))->promise(); } /** * Creates and executes a command for an operation by name. * * @param string $name Name of the command to execute. * @param array $args Arguments to pass to the getCommand method. * * @return ResultInterface|PromiseInterface * @see \GuzzleHttp\Command\ServiceClientInterface::getCommand */ public function __call($name, array $args) { $args = isset($args[0]) ? $args[0] : []; if (substr($name, -5) === 'Async') { $command = $this->getCommand(substr($name, 0, -5), $args); return $this->executeAsync($command); } else { return $this->execute($this->getCommand($name, $args)); } } /** * Defines the main handler for commands that uses the HTTP client. * * @return callable */ private function createCommandHandler() { return function (CommandInterface $command) { return Promise\coroutine(function () use ($command) { // Prepare the HTTP options. $opts = $command['@http'] ?: []; unset($command['@http']); try { // Prepare the request from the command and send it. $request = $this->transformCommandToRequest($command); $promise = $this->httpClient->sendAsync($request, $opts); // Create a result from the response. $response = (yield $promise); yield $this->transformResponseToResult($response, $request, $command); } catch (\Exception $e) { throw CommandException::fromPrevious($command, $e); } }); }; } /** * Transforms a Command object into a Request object. * * @param CommandInterface $command * @return RequestInterface */ private function transformCommandToRequest(CommandInterface $command) { $transform = $this->commandToRequestTransformer; return $transform($command); } /** * Transforms a Response object, also using data from the Request object, * into a Result object. * * @param ResponseInterface $response * @param RequestInterface $request * @param CommandInterface $command * @return ResultInterface */ private function transformResponseToResult( ResponseInterface $response, RequestInterface $request, CommandInterface $command ) { $transform = $this->responseToResultTransformer; return $transform($response, $request, $command); } } src/ServiceClientInterface.php000064400000006136144760114660012443 0ustar00