1: <?php
2:
3: namespace Guzzle\Service\Command;
4:
5: use Guzzle\Common\Collection;
6: use Guzzle\Common\Exception\InvalidArgumentException;
7: use Guzzle\Http\Message\Response;
8: use Guzzle\Http\Message\RequestInterface;
9: use Guzzle\Http\Curl\CurlHandle;
10: use Guzzle\Service\Client;
11: use Guzzle\Service\ClientInterface;
12: use Guzzle\Service\Description\Operation;
13: use Guzzle\Service\Description\OperationInterface;
14: use Guzzle\Service\Description\ValidatorInterface;
15: use Guzzle\Service\Description\SchemaValidator;
16: use Guzzle\Service\Exception\CommandException;
17: use Guzzle\Service\Exception\ValidationException;
18:
19: 20: 21:
22: abstract class AbstractCommand extends Collection implements CommandInterface
23: {
24:
25: const = 'command.headers';
26:
27: const ON_COMPLETE = 'command.on_complete';
28:
29: const DISABLE_VALIDATION = 'command.disable_validation';
30:
31: const RESPONSE_PROCESSING = 'command.response_processing';
32:
33: const TYPE_RAW = 'raw';
34: const TYPE_MODEL = 'model';
35: const TYPE_NO_TRANSLATION = 'no_translation';
36:
37: const RESPONSE_BODY = 'command.response_body';
38:
39: 40: 41:
42: protected $client;
43:
44: 45: 46:
47: protected $request;
48:
49: 50: 51:
52: protected $result;
53:
54: 55: 56:
57: protected $operation;
58:
59: 60: 61:
62: protected $onComplete;
63:
64: 65: 66:
67: protected $validator;
68:
69: 70: 71: 72: 73: 74:
75: public function __construct($parameters = null, OperationInterface $operation = null)
76: {
77: parent::__construct($parameters);
78: $this->operation = $operation ?: $this->createOperation();
79: foreach ($this->operation->getParams() as $name => $arg) {
80: $currentValue = $this->get($name);
81: $configValue = $arg->getValue($currentValue);
82:
83: if ($currentValue !== $configValue) {
84: $this->set($name, $configValue);
85: }
86: }
87:
88: $headers = $this->get(self::HEADERS_OPTION);
89: if (!$headers instanceof Collection) {
90: $this->set(self::HEADERS_OPTION, new Collection((array) $headers));
91: }
92:
93:
94: if ($onComplete = $this->get('command.on_complete')) {
95: $this->remove('command.on_complete');
96: $this->setOnComplete($onComplete);
97: }
98:
99: $this->init();
100: }
101:
102: 103: 104:
105: public function __clone()
106: {
107: $this->request = null;
108: $this->result = null;
109: }
110:
111: 112: 113: 114: 115:
116: public function __invoke()
117: {
118: return $this->execute();
119: }
120:
121: 122: 123:
124: public function getName()
125: {
126: return $this->operation->getName();
127: }
128:
129: 130: 131: 132: 133:
134: public function getOperation()
135: {
136: return $this->operation;
137: }
138:
139: 140: 141:
142: public function setOnComplete($callable)
143: {
144: if (!is_callable($callable)) {
145: throw new InvalidArgumentException('The onComplete function must be callable');
146: }
147:
148: $this->onComplete = $callable;
149:
150: return $this;
151: }
152:
153: 154: 155:
156: public function execute()
157: {
158: if (!$this->client) {
159: throw new CommandException('A client must be associated with the command before it can be executed.');
160: }
161:
162: return $this->client->execute($this);
163: }
164:
165: 166: 167:
168: public function getClient()
169: {
170: return $this->client;
171: }
172:
173: 174: 175:
176: public function setClient(ClientInterface $client)
177: {
178: $this->client = $client;
179:
180: return $this;
181: }
182:
183: 184: 185:
186: public function getRequest()
187: {
188: if (!$this->request) {
189: throw new CommandException('The command must be prepared before retrieving the request');
190: }
191:
192: return $this->request;
193: }
194:
195: 196: 197:
198: public function getResponse()
199: {
200: if (!$this->isExecuted()) {
201: $this->execute();
202: }
203:
204: return $this->request->getResponse();
205: }
206:
207: 208: 209:
210: public function getResult()
211: {
212: if (!$this->isExecuted()) {
213: $this->execute();
214: }
215:
216: if (null === $this->result) {
217: $this->process();
218:
219: if ($this->onComplete) {
220: call_user_func($this->onComplete, $this);
221: }
222: }
223:
224: return $this->result;
225: }
226:
227: 228: 229:
230: public function setResult($result)
231: {
232: $this->result = $result;
233:
234: return $this;
235: }
236:
237: 238: 239:
240: public function isPrepared()
241: {
242: return $this->request !== null;
243: }
244:
245: 246: 247:
248: public function isExecuted()
249: {
250: return $this->request !== null && $this->request->getState() == 'complete';
251: }
252:
253: 254: 255:
256: public function prepare()
257: {
258: if (!$this->isPrepared()) {
259: if (!$this->client) {
260: throw new CommandException('A client must be associated with the command before it can be prepared.');
261: }
262:
263:
264: if (!$this->hasKey(self::RESPONSE_PROCESSING)) {
265: $this->set(self::RESPONSE_PROCESSING, self::TYPE_MODEL);
266: }
267:
268:
269: $this->client->dispatch('command.before_prepare', array('command' => $this));
270:
271:
272: $this->validate();
273:
274: $this->build();
275:
276:
277: if ($headers = $this->get(self::HEADERS_OPTION)) {
278: foreach ($headers as $key => $value) {
279: $this->request->setHeader($key, $value);
280: }
281: }
282:
283:
284: if ($options = $this->get(Client::CURL_OPTIONS)) {
285: $this->request->getCurlOptions()->merge(CurlHandle::parseCurlConfig($options));
286: }
287:
288:
289: if ($responseBody = $this->get(self::RESPONSE_BODY)) {
290: $this->request->setResponseBody($responseBody);
291: }
292:
293: $this->client->dispatch('command.after_prepare', array('command' => $this));
294: }
295:
296: return $this->request;
297: }
298:
299: 300: 301: 302: 303: 304: 305: 306:
307: public function setValidator(ValidatorInterface $validator)
308: {
309: $this->validator = $validator;
310:
311: return $this;
312: }
313:
314: 315: 316:
317: public function ()
318: {
319: return $this->get(self::HEADERS_OPTION);
320: }
321:
322: 323: 324:
325: protected function init() {}
326:
327: 328: 329:
330: abstract protected function build();
331:
332: 333: 334: 335: 336:
337: protected function createOperation()
338: {
339: return new Operation(array('name' => get_class($this)));
340: }
341:
342: 343: 344: 345:
346: protected function process()
347: {
348: $this->result = $this->get(self::RESPONSE_PROCESSING) != self::TYPE_RAW
349: ? DefaultResponseParser::getInstance()->parse($this)
350: : $this->request->getResponse();
351: }
352:
353: 354: 355: 356: 357:
358: protected function validate()
359: {
360:
361: if ($this->get(self::DISABLE_VALIDATION)) {
362: return;
363: }
364:
365: $errors = array();
366: $validator = $this->getValidator();
367: foreach ($this->operation->getParams() as $name => $schema) {
368: $value = $this->get($name);
369: if (!$validator->validate($schema, $value)) {
370: $errors = array_merge($errors, $validator->getErrors());
371: } elseif ($value !== $this->get($name)) {
372:
373: $this->data[$name] = $value;
374: }
375: }
376:
377:
378: if ($properties = $this->operation->getAdditionalParameters()) {
379: foreach ($this->getAll() as $name => $value) {
380:
381: if (!$this->operation->hasParam($name)) {
382:
383: $properties->setName($name);
384: if (!$validator->validate($properties, $value)) {
385: $errors = array_merge($errors, $validator->getErrors());
386: } elseif ($value !== $this->get($name)) {
387: $this->data[$name] = $value;
388: }
389: }
390: }
391: }
392:
393: if (!empty($errors)) {
394: $e = new ValidationException('Validation errors: ' . implode("\n", $errors));
395: $e->setErrors($errors);
396: throw $e;
397: }
398: }
399:
400: 401: 402: 403: 404: 405:
406: protected function getValidator()
407: {
408: if (!$this->validator) {
409: $this->validator = SchemaValidator::getInstance();
410: }
411:
412: return $this->validator;
413: }
414: }
415: