1: <?php
2:
3: namespace Guzzle\Service\Description;
4:
5: use Guzzle\Common\Exception\InvalidArgumentException;
6:
7: /**
8: * Data object holding the information of an API command
9: */
10: class Operation implements OperationInterface
11: {
12: /**
13: * @var string Default command class to use when none is specified
14: */
15: const DEFAULT_COMMAND_CLASS = 'Guzzle\\Service\\Command\\OperationCommand';
16:
17: /**
18: * @var array Hashmap of properties that can be specified. Represented as a hash to speed up constructor.
19: */
20: protected static $properties = array(
21: 'name' => true, 'httpMethod' => true, 'uri' => true, 'class' => true, 'responseClass' => true,
22: 'responseType' => true, 'responseNotes' => true, 'notes' => true, 'summary' => true, 'documentationUrl' => true,
23: 'deprecated' => true, 'data' => true, 'parameters' => true, 'additionalParameters' => true,
24: 'errorResponses' => true
25: );
26:
27: /**
28: * @var array Parameters
29: */
30: protected $parameters = array();
31:
32: /**
33: * @var Parameter Additional parameters schema
34: */
35: protected $additionalParameters;
36:
37: /**
38: * @var string Name of the command
39: */
40: protected $name;
41:
42: /**
43: * @var string HTTP method
44: */
45: protected $httpMethod;
46:
47: /**
48: * @var string This is a short summary of what the operation does
49: */
50: protected $summary;
51:
52: /**
53: * @var string A longer text field to explain the behavior of the operation.
54: */
55: protected $notes;
56:
57: /**
58: * @var string Reference URL providing more information about the operation
59: */
60: protected $documentationUrl;
61:
62: /**
63: * @var string HTTP URI of the command
64: */
65: protected $uri;
66:
67: /**
68: * @var string Class of the command object
69: */
70: protected $class;
71:
72: /**
73: * @var string This is what is returned from the method
74: */
75: protected $responseClass;
76:
77: /**
78: * @var string Type information about the response
79: */
80: protected $responseType;
81:
82: /**
83: * @var string Information about the response returned by the operation
84: */
85: protected $responseNotes;
86:
87: /**
88: * @var bool Whether or not the command is deprecated
89: */
90: protected $deprecated;
91:
92: /**
93: * @var array Array of errors that could occur when running the command
94: */
95: protected $errorResponses;
96:
97: /**
98: * @var ServiceDescriptionInterface
99: */
100: protected $description;
101:
102: /**
103: * @var array Extra operation information
104: */
105: protected $data;
106:
107: /**
108: * Builds an Operation object using an array of configuration data:
109: * - name: (string) Name of the command
110: * - httpMethod: (string) HTTP method of the operation
111: * - uri: (string) URI template that can create a relative or absolute URL
112: * - class: (string) Concrete class that implements this command
113: * - parameters: (array) Associative array of parameters for the command. {@see Parameter} for information.
114: * - summary: (string) This is a short summary of what the operation does
115: * - notes: (string) A longer text field to explain the behavior of the operation.
116: * - documentationUrl: (string) Reference URL providing more information about the operation
117: * - responseClass: (string) This is what is returned from the method. Can be a primitive, PSR-0 compliant
118: * class name, or model.
119: * - responseNotes: (string) Information about the response returned by the operation
120: * - responseType: (string) One of 'primitive', 'class', 'model', or 'documentation'. If not specified, this
121: * value will be automatically inferred based on whether or not there is a model matching the
122: * name, if a matching PSR-0 compliant class name is found, or set to 'primitive' by default.
123: * - deprecated: (bool) Set to true if this is a deprecated command
124: * - errorResponses: (array) Errors that could occur when executing the command. Array of hashes, each with a
125: * 'code' (the HTTP response code), 'phrase' (response reason phrase or description of the
126: * error), and 'class' (a custom exception class that would be thrown if the error is
127: * encountered).
128: * - data: (array) Any extra data that might be used to help build or serialize the operation
129: * - additionalParameters: (null|array) Parameter schema to use when an option is passed to the operation that is
130: * not in the schema
131: *
132: * @param array $config Array of configuration data
133: * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found
134: */
135: public function __construct(array $config = array(), ServiceDescriptionInterface $description = null)
136: {
137: $this->description = $description;
138:
139: // Get the intersection of the available properties and properties set on the operation
140: foreach (array_intersect_key($config, self::$properties) as $key => $value) {
141: $this->{$key} = $value;
142: }
143:
144: $this->class = $this->class ?: self::DEFAULT_COMMAND_CLASS;
145: $this->deprecated = (bool) $this->deprecated;
146: $this->errorResponses = $this->errorResponses ?: array();
147: $this->data = $this->data ?: array();
148:
149: if (!$this->responseClass) {
150: $this->responseClass = 'array';
151: $this->responseType = 'primitive';
152: } elseif ($this->responseType) {
153: // Set the response type to perform validation
154: $this->setResponseType($this->responseType);
155: } else {
156: // A response class was set and no response type was set, so guess what the type is
157: $this->inferResponseType();
158: }
159:
160: // Parameters need special handling when adding
161: if (!empty($config['parameters'])) {
162: $this->parameters = array();
163: foreach ($config['parameters'] as $name => $param) {
164: if ($param instanceof Parameter) {
165: $param->setName($name)->setParent($this);
166: $this->parameters[$name] = $param;
167: } elseif (is_array($param)) {
168: $param['name'] = $name;
169: $this->addParam(new Parameter($param, $this->description));
170: }
171: }
172: }
173:
174: if (isset($config['additionalParameters'])) {
175: if ($config['additionalParameters'] instanceof Parameter) {
176: $this->setadditionalParameters($config['additionalParameters']);
177: } elseif (is_array($config['additionalParameters'])) {
178: $this->setadditionalParameters(new Parameter($config['additionalParameters'], $this->description));
179: }
180: }
181: }
182:
183: /**
184: * {@inheritdoc}
185: */
186: public function toArray()
187: {
188: $result = array();
189: // Grab valid properties and filter out values that weren't set
190: foreach (array_keys(self::$properties) as $check) {
191: if ($value = $this->{$check}) {
192: $result[$check] = $value;
193: }
194: }
195: // Remove the name property
196: unset($result['name']);
197: // Parameters need to be converted to arrays
198: $result['parameters'] = array();
199: foreach ($this->parameters as $key => $param) {
200: $result['parameters'][$key] = $param->toArray();
201: }
202: // Additional parameters need to be cast to an array
203: if ($this->additionalParameters instanceof Parameter) {
204: $result['additionalParameters'] = $this->additionalParameters->toArray();
205: }
206:
207: return $result;
208: }
209:
210: /**
211: * {@inheritdoc}
212: */
213: public function getServiceDescription()
214: {
215: return $this->description;
216: }
217:
218: /**
219: * {@inheritdoc}
220: */
221: public function setServiceDescription(ServiceDescriptionInterface $description)
222: {
223: $this->description = $description;
224:
225: return $this;
226: }
227:
228: /**
229: * {@inheritdoc}
230: */
231: public function getParams()
232: {
233: return $this->parameters;
234: }
235:
236: /**
237: * {@inheritdoc}
238: */
239: public function getParamNames()
240: {
241: return array_keys($this->parameters);
242: }
243:
244: /**
245: * {@inheritdoc}
246: */
247: public function hasParam($name)
248: {
249: return isset($this->parameters[$name]);
250: }
251:
252: /**
253: * {@inheritdoc}
254: */
255: public function getParam($param)
256: {
257: return isset($this->parameters[$param]) ? $this->parameters[$param] : null;
258: }
259:
260: /**
261: * Add a parameter to the command
262: *
263: * @param Parameter $param Parameter to add
264: *
265: * @return self
266: */
267: public function addParam(Parameter $param)
268: {
269: $this->parameters[$param->getName()] = $param;
270: $param->setParent($this);
271:
272: return $this;
273: }
274:
275: /**
276: * Remove a parameter from the command
277: *
278: * @param string $name Name of the parameter to remove
279: *
280: * @return self
281: */
282: public function removeParam($name)
283: {
284: unset($this->parameters[$name]);
285:
286: return $this;
287: }
288:
289: /**
290: * {@inheritdoc}
291: */
292: public function getHttpMethod()
293: {
294: return $this->httpMethod;
295: }
296:
297: /**
298: * Set the HTTP method of the command
299: *
300: * @param string $httpMethod Method to set
301: *
302: * @return self
303: */
304: public function setHttpMethod($httpMethod)
305: {
306: $this->httpMethod = $httpMethod;
307:
308: return $this;
309: }
310:
311: /**
312: * {@inheritdoc}
313: */
314: public function getClass()
315: {
316: return $this->class;
317: }
318:
319: /**
320: * Set the concrete class of the command
321: *
322: * @param string $className Concrete class name
323: *
324: * @return self
325: */
326: public function setClass($className)
327: {
328: $this->class = $className;
329:
330: return $this;
331: }
332:
333: /**
334: * {@inheritdoc}
335: */
336: public function getName()
337: {
338: return $this->name;
339: }
340:
341: /**
342: * Set the name of the command
343: *
344: * @param string $name Name of the command
345: *
346: * @return self
347: */
348: public function setName($name)
349: {
350: $this->name = $name;
351:
352: return $this;
353: }
354:
355: /**
356: * {@inheritdoc}
357: */
358: public function getSummary()
359: {
360: return $this->summary;
361: }
362:
363: /**
364: * Set a short summary of what the operation does
365: *
366: * @param string $summary Short summary of the operation
367: *
368: * @return self
369: */
370: public function setSummary($summary)
371: {
372: $this->summary = $summary;
373:
374: return $this;
375: }
376:
377: /**
378: * {@inheritdoc}
379: */
380: public function getNotes()
381: {
382: return $this->notes;
383: }
384:
385: /**
386: * Set a longer text field to explain the behavior of the operation.
387: *
388: * @param string $notes Notes on the operation
389: *
390: * @return self
391: */
392: public function setNotes($notes)
393: {
394: $this->notes = $notes;
395:
396: return $this;
397: }
398:
399: /**
400: * {@inheritdoc}
401: */
402: public function getDocumentationUrl()
403: {
404: return $this->documentationUrl;
405: }
406:
407: /**
408: * Set the URL pointing to additional documentation on the command
409: *
410: * @param string $docUrl Documentation URL
411: *
412: * @return self
413: */
414: public function setDocumentationUrl($docUrl)
415: {
416: $this->documentationUrl = $docUrl;
417:
418: return $this;
419: }
420:
421: /**
422: * {@inheritdoc}
423: */
424: public function getResponseClass()
425: {
426: return $this->responseClass;
427: }
428:
429: /**
430: * Set what is returned from the method. Can be a primitive, class name, or model. For example: 'array',
431: * 'Guzzle\\Foo\\Baz', or 'MyModelName' (to reference a model by ID).
432: *
433: * @param string $responseClass Type of response
434: *
435: * @return self
436: */
437: public function setResponseClass($responseClass)
438: {
439: $this->responseClass = $responseClass;
440: $this->inferResponseType();
441:
442: return $this;
443: }
444:
445: /**
446: * {@inheritdoc}
447: */
448: public function getResponseType()
449: {
450: return $this->responseType;
451: }
452:
453: /**
454: * Set qualifying information about the responseClass. One of 'primitive', 'class', 'model', or 'documentation'
455: *
456: * @param string $responseType Response type information
457: *
458: * @return self
459: * @throws InvalidArgumentException
460: */
461: public function setResponseType($responseType)
462: {
463: static $types = array(
464: self::TYPE_PRIMITIVE => true,
465: self::TYPE_CLASS => true,
466: self::TYPE_MODEL => true,
467: self::TYPE_DOCUMENTATION => true
468: );
469: if (!isset($types[$responseType])) {
470: throw new InvalidArgumentException('responseType must be one of ' . implode(', ', array_keys($types)));
471: }
472:
473: $this->responseType = $responseType;
474:
475: return $this;
476: }
477:
478: /**
479: * {@inheritdoc}
480: */
481: public function getResponseNotes()
482: {
483: return $this->responseNotes;
484: }
485:
486: /**
487: * Set notes about the response of the operation
488: *
489: * @param string $notes Response notes
490: *
491: * @return self
492: */
493: public function setResponseNotes($notes)
494: {
495: $this->responseNotes = $notes;
496:
497: return $this;
498: }
499:
500: /**
501: * {@inheritdoc}
502: */
503: public function getDeprecated()
504: {
505: return $this->deprecated;
506: }
507:
508: /**
509: * Set whether or not the command is deprecated
510: *
511: * @param bool $isDeprecated Set to true to mark as deprecated
512: *
513: * @return self
514: */
515: public function setDeprecated($isDeprecated)
516: {
517: $this->deprecated = $isDeprecated;
518:
519: return $this;
520: }
521:
522: /**
523: * {@inheritdoc}
524: */
525: public function getUri()
526: {
527: return $this->uri;
528: }
529:
530: /**
531: * Set the URI template of the command
532: *
533: * @param string $uri URI template to set
534: *
535: * @return self
536: */
537: public function setUri($uri)
538: {
539: $this->uri = $uri;
540:
541: return $this;
542: }
543:
544: /**
545: * {@inheritdoc}
546: */
547: public function getErrorResponses()
548: {
549: return $this->errorResponses;
550: }
551:
552: /**
553: * Add an error to the command
554: *
555: * @param string $code HTTP response code
556: * @param string $reason HTTP response reason phrase or information about the error
557: * @param string $class Exception class associated with the error
558: *
559: * @return self
560: */
561: public function addErrorResponse($code, $reason, $class)
562: {
563: $this->errorResponses[] = array('code' => $code, 'reason' => $reason, 'class' => $class);
564:
565: return $this;
566: }
567:
568: /**
569: * Set all of the error responses of the operation
570: *
571: * @param array $errorResponses Hash of error name to a hash containing a code, reason, class
572: *
573: * @return self
574: */
575: public function setErrorResponses(array $errorResponses)
576: {
577: $this->errorResponses = $errorResponses;
578:
579: return $this;
580: }
581:
582: /**
583: * {@inheritdoc}
584: */
585: public function getData($name)
586: {
587: return isset($this->data[$name]) ? $this->data[$name] : null;
588: }
589:
590: /**
591: * Set a particular data point on the operation
592: *
593: * @param string $name Name of the data value
594: * @param mixed $value Value to set
595: *
596: * @return self
597: */
598: public function setData($name, $value)
599: {
600: $this->data[$name] = $value;
601:
602: return $this;
603: }
604:
605: /**
606: * Get the additionalParameters of the operation
607: *
608: * @return Paramter|null
609: */
610: public function getAdditionalParameters()
611: {
612: return $this->additionalParameters;
613: }
614:
615: /**
616: * Set the additionalParameters of the operation
617: *
618: * @param Parameter|null $parameter Parameter to set
619: *
620: * @return self
621: */
622: public function setAdditionalParameters($parameter)
623: {
624: if ($this->additionalParameters = $parameter) {
625: $this->additionalParameters->setParent($this);
626: }
627:
628: return $this;
629: }
630:
631: /**
632: * Infer the response type from the responseClass value
633: */
634: protected function inferResponseType()
635: {
636: if (!$this->responseClass || $this->responseClass == 'array' || $this->responseClass == 'string'
637: || $this->responseClass == 'boolean' || $this->responseClass == 'integer'
638: ) {
639: $this->responseType = self::TYPE_PRIMITIVE;
640: } elseif ($this->description && $this->description->hasModel($this->responseClass)) {
641: $this->responseType = self::TYPE_MODEL;
642: } elseif (strpos($this->responseClass, '\\') !== false) {
643: $this->responseType = self::TYPE_CLASS;
644: } else {
645: $this->responseType = self::TYPE_PRIMITIVE;
646: }
647: }
648: }
649: