PageRenderTime 112ms CodeModel.GetById 45ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/Guzzle/Service/Command/AbstractCommand.php

https://gitlab.com/x33n/ampache
PHP | 390 lines | 239 code | 63 blank | 88 comment | 29 complexity | 5eabfb0603f0deaaf9bfb17ab9297e63 MD5 | raw file
  1. <?php
  2. namespace Guzzle\Service\Command;
  3. use Guzzle\Common\Collection;
  4. use Guzzle\Common\Exception\InvalidArgumentException;
  5. use Guzzle\Http\Message\RequestInterface;
  6. use Guzzle\Http\Curl\CurlHandle;
  7. use Guzzle\Service\Client;
  8. use Guzzle\Service\ClientInterface;
  9. use Guzzle\Service\Description\Operation;
  10. use Guzzle\Service\Description\OperationInterface;
  11. use Guzzle\Service\Description\ValidatorInterface;
  12. use Guzzle\Service\Description\SchemaValidator;
  13. use Guzzle\Service\Exception\CommandException;
  14. use Guzzle\Service\Exception\ValidationException;
  15. /**
  16. * Command object to handle preparing and processing client requests and responses of the requests
  17. */
  18. abstract class AbstractCommand extends Collection implements CommandInterface
  19. {
  20. // @deprecated: Option used to specify custom headers to add to the generated request
  21. const HEADERS_OPTION = 'command.headers';
  22. // @deprecated: Option used to add an onComplete method to a command
  23. const ON_COMPLETE = 'command.on_complete';
  24. // @deprecated: Option used to change the entity body used to store a response
  25. const RESPONSE_BODY = 'command.response_body';
  26. // Option used to add request options to the request created by a command
  27. const REQUEST_OPTIONS = 'command.request_options';
  28. // command values to not count as additionalParameters
  29. const HIDDEN_PARAMS = 'command.hidden_params';
  30. // Option used to disable any pre-sending command validation
  31. const DISABLE_VALIDATION = 'command.disable_validation';
  32. // Option used to override how a command result will be formatted
  33. const RESPONSE_PROCESSING = 'command.response_processing';
  34. // Different response types that commands can use
  35. const TYPE_RAW = 'raw';
  36. const TYPE_MODEL = 'model';
  37. const TYPE_NO_TRANSLATION = 'no_translation';
  38. /** @var ClientInterface Client object used to execute the command */
  39. protected $client;
  40. /** @var RequestInterface The request object associated with the command */
  41. protected $request;
  42. /** @var mixed The result of the command */
  43. protected $result;
  44. /** @var OperationInterface API information about the command */
  45. protected $operation;
  46. /** @var mixed callable */
  47. protected $onComplete;
  48. /** @var ValidatorInterface Validator used to prepare and validate properties against a JSON schema */
  49. protected $validator;
  50. /**
  51. * @param array|Collection $parameters Collection of parameters to set on the command
  52. * @param OperationInterface $operation Command definition from description
  53. */
  54. public function __construct($parameters = array(), OperationInterface $operation = null)
  55. {
  56. parent::__construct($parameters);
  57. $this->operation = $operation ?: $this->createOperation();
  58. foreach ($this->operation->getParams() as $name => $arg) {
  59. $currentValue = $this[$name];
  60. $configValue = $arg->getValue($currentValue);
  61. // If default or static values are set, then this should always be updated on the config object
  62. if ($currentValue !== $configValue) {
  63. $this[$name] = $configValue;
  64. }
  65. }
  66. $headers = $this[self::HEADERS_OPTION];
  67. if (!$headers instanceof Collection) {
  68. $this[self::HEADERS_OPTION] = new Collection((array) $headers);
  69. }
  70. // You can set a command.on_complete option in your parameters to set an onComplete callback
  71. if ($onComplete = $this['command.on_complete']) {
  72. unset($this['command.on_complete']);
  73. $this->setOnComplete($onComplete);
  74. }
  75. // Set the hidden additional parameters
  76. if (!$this[self::HIDDEN_PARAMS]) {
  77. $this[self::HIDDEN_PARAMS] = array(
  78. self::HEADERS_OPTION,
  79. self::RESPONSE_PROCESSING,
  80. self::HIDDEN_PARAMS,
  81. self::REQUEST_OPTIONS
  82. );
  83. }
  84. $this->init();
  85. }
  86. /**
  87. * Custom clone behavior
  88. */
  89. public function __clone()
  90. {
  91. $this->request = null;
  92. $this->result = null;
  93. }
  94. /**
  95. * Execute the command in the same manner as calling a function
  96. *
  97. * @return mixed Returns the result of {@see AbstractCommand::execute}
  98. */
  99. public function __invoke()
  100. {
  101. return $this->execute();
  102. }
  103. public function getName()
  104. {
  105. return $this->operation->getName();
  106. }
  107. /**
  108. * Get the API command information about the command
  109. *
  110. * @return OperationInterface
  111. */
  112. public function getOperation()
  113. {
  114. return $this->operation;
  115. }
  116. public function setOnComplete($callable)
  117. {
  118. if (!is_callable($callable)) {
  119. throw new InvalidArgumentException('The onComplete function must be callable');
  120. }
  121. $this->onComplete = $callable;
  122. return $this;
  123. }
  124. public function execute()
  125. {
  126. if (!$this->client) {
  127. throw new CommandException('A client must be associated with the command before it can be executed.');
  128. }
  129. return $this->client->execute($this);
  130. }
  131. public function getClient()
  132. {
  133. return $this->client;
  134. }
  135. public function setClient(ClientInterface $client)
  136. {
  137. $this->client = $client;
  138. return $this;
  139. }
  140. public function getRequest()
  141. {
  142. if (!$this->request) {
  143. throw new CommandException('The command must be prepared before retrieving the request');
  144. }
  145. return $this->request;
  146. }
  147. public function getResponse()
  148. {
  149. if (!$this->isExecuted()) {
  150. $this->execute();
  151. }
  152. return $this->request->getResponse();
  153. }
  154. public function getResult()
  155. {
  156. if (!$this->isExecuted()) {
  157. $this->execute();
  158. }
  159. if (null === $this->result) {
  160. $this->process();
  161. // Call the onComplete method if one is set
  162. if ($this->onComplete) {
  163. call_user_func($this->onComplete, $this);
  164. }
  165. }
  166. return $this->result;
  167. }
  168. public function setResult($result)
  169. {
  170. $this->result = $result;
  171. return $this;
  172. }
  173. public function isPrepared()
  174. {
  175. return $this->request !== null;
  176. }
  177. public function isExecuted()
  178. {
  179. return $this->request !== null && $this->request->getState() == 'complete';
  180. }
  181. public function prepare()
  182. {
  183. if (!$this->isPrepared()) {
  184. if (!$this->client) {
  185. throw new CommandException('A client must be associated with the command before it can be prepared.');
  186. }
  187. // If no response processing value was specified, then attempt to use the highest level of processing
  188. if (!isset($this[self::RESPONSE_PROCESSING])) {
  189. $this[self::RESPONSE_PROCESSING] = self::TYPE_MODEL;
  190. }
  191. // Notify subscribers of the client that the command is being prepared
  192. $this->client->dispatch('command.before_prepare', array('command' => $this));
  193. // Fail on missing required arguments, and change parameters via filters
  194. $this->validate();
  195. // Delegate to the subclass that implements the build method
  196. $this->build();
  197. // Add custom request headers set on the command
  198. if ($headers = $this[self::HEADERS_OPTION]) {
  199. foreach ($headers as $key => $value) {
  200. $this->request->setHeader($key, $value);
  201. }
  202. }
  203. // Add any curl options to the request
  204. if ($options = $this[Client::CURL_OPTIONS]) {
  205. $this->request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($options));
  206. }
  207. // Set a custom response body
  208. if ($responseBody = $this[self::RESPONSE_BODY]) {
  209. $this->request->setResponseBody($responseBody);
  210. }
  211. $this->client->dispatch('command.after_prepare', array('command' => $this));
  212. }
  213. return $this->request;
  214. }
  215. /**
  216. * Set the validator used to validate and prepare command parameters and nested JSON schemas. If no validator is
  217. * set, then the command will validate using the default {@see SchemaValidator}.
  218. *
  219. * @param ValidatorInterface $validator Validator used to prepare and validate properties against a JSON schema
  220. *
  221. * @return self
  222. */
  223. public function setValidator(ValidatorInterface $validator)
  224. {
  225. $this->validator = $validator;
  226. return $this;
  227. }
  228. public function getRequestHeaders()
  229. {
  230. return $this[self::HEADERS_OPTION];
  231. }
  232. /**
  233. * Initialize the command (hook that can be implemented in subclasses)
  234. */
  235. protected function init() {}
  236. /**
  237. * Create the request object that will carry out the command
  238. */
  239. abstract protected function build();
  240. /**
  241. * Hook used to create an operation for concrete commands that are not associated with a service description
  242. *
  243. * @return OperationInterface
  244. */
  245. protected function createOperation()
  246. {
  247. return new Operation(array('name' => get_class($this)));
  248. }
  249. /**
  250. * Create the result of the command after the request has been completed.
  251. * Override this method in subclasses to customize this behavior
  252. */
  253. protected function process()
  254. {
  255. $this->result = $this[self::RESPONSE_PROCESSING] != self::TYPE_RAW
  256. ? DefaultResponseParser::getInstance()->parse($this)
  257. : $this->request->getResponse();
  258. }
  259. /**
  260. * Validate and prepare the command based on the schema and rules defined by the command's Operation object
  261. *
  262. * @throws ValidationException when validation errors occur
  263. */
  264. protected function validate()
  265. {
  266. // Do not perform request validation/transformation if it is disable
  267. if ($this[self::DISABLE_VALIDATION]) {
  268. return;
  269. }
  270. $errors = array();
  271. $validator = $this->getValidator();
  272. foreach ($this->operation->getParams() as $name => $schema) {
  273. $value = $this[$name];
  274. if (!$validator->validate($schema, $value)) {
  275. $errors = array_merge($errors, $validator->getErrors());
  276. } elseif ($value !== $this[$name]) {
  277. // Update the config value if it changed and no validation errors were encountered
  278. $this->data[$name] = $value;
  279. }
  280. }
  281. // Validate additional parameters
  282. $hidden = $this[self::HIDDEN_PARAMS];
  283. if ($properties = $this->operation->getAdditionalParameters()) {
  284. foreach ($this->toArray() as $name => $value) {
  285. // It's only additional if it isn't defined in the schema
  286. if (!$this->operation->hasParam($name) && !in_array($name, $hidden)) {
  287. // Always set the name so that error messages are useful
  288. $properties->setName($name);
  289. if (!$validator->validate($properties, $value)) {
  290. $errors = array_merge($errors, $validator->getErrors());
  291. } elseif ($value !== $this[$name]) {
  292. $this->data[$name] = $value;
  293. }
  294. }
  295. }
  296. }
  297. if (!empty($errors)) {
  298. $e = new ValidationException('Validation errors: ' . implode("\n", $errors));
  299. $e->setErrors($errors);
  300. throw $e;
  301. }
  302. }
  303. /**
  304. * Get the validator used to prepare and validate properties. If no validator has been set on the command, then
  305. * the default {@see SchemaValidator} will be used.
  306. *
  307. * @return ValidatorInterface
  308. */
  309. protected function getValidator()
  310. {
  311. if (!$this->validator) {
  312. $this->validator = SchemaValidator::getInstance();
  313. }
  314. return $this->validator;
  315. }
  316. /**
  317. * Get array of any validation errors
  318. * If no validator has been set then return false
  319. */
  320. public function getValidationErrors()
  321. {
  322. if (!$this->validator) {
  323. return false;
  324. }
  325. return $this->validator->getErrors();
  326. }
  327. }