1: <?php
2:
3: namespace Guzzle\Service\Description;
4:
5: use Guzzle\Common\Exception\InvalidArgumentException;
6:
7: /**
8: * API parameter object used with service descriptions
9: */
10: class Parameter
11: {
12: protected $name;
13: protected $description;
14: protected $serviceDescription;
15: protected $type;
16: protected $required;
17: protected $enum;
18: protected $pattern;
19: protected $minimum;
20: protected $maximum;
21: protected $minLength;
22: protected $maxLength;
23: protected $minItems;
24: protected $maxItems;
25: protected $default;
26: protected $static;
27: protected $instanceOf;
28: protected $filters;
29: protected $location;
30: protected $sentAs;
31: protected $data;
32: protected $properties = array();
33: protected $additionalProperties;
34: protected $items;
35: protected $parent;
36: protected $ref;
37: protected $format;
38: protected $propertiesCache = null;
39:
40: /**
41: * Create a new Parameter using an associative array of data. The array can contain the following information:
42: * - name: (string) Unique name of the parameter
43: * - type: (string|array) Type of variable (string, number, integer, boolean, object, array, numeric,
44: * null, any). Types are using for validation and determining the structure of a parameter. You
45: * can use a union type by providing an array of simple types. If one of the union types matches
46: * the provided value, then the value is valid.
47: * - instanceOf: (string) When the type is an object, you can specify the class that the object must implement
48: * - required: (bool) Whether or not the parameter is required
49: * - default: (mixed) Default value to use if no value is supplied
50: * - static: (bool) Set to true to specify that the parameter value cannot be changed from the default
51: * - description: (string) Documentation of the parameter
52: * - location: (string) The location of a request used to apply a parameter. Custom locations can be registered
53: * with a command, but the defaults are uri, query, header, body, json, xml, postField, postFile.
54: * - sentAs: (string) Specifies how the data being modeled is sent over the wire. For example, you may wish
55: * to include certain headers in a response model that have a normalized casing of FooBar, but the
56: * actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar.
57: * - filters: (array) Array of static method names to to run a parameter value through. Each value in the
58: * array must be a string containing the full class path to a static method or an array of complex
59: * filter information. You can specify static methods of classes using the full namespace class
60: * name followed by '::' (e.g. Foo\Bar::baz()). Some filters require arguments in order to properly
61: * filter a value. For complex filters, use a hash containing a 'method' key pointing to a static
62: * method, and an 'args' key containing an array of positional arguments to pass to the method.
63: * Arguments can contain keywords that are replaced when filtering a value: '@value' is replaced
64: * with the value being validated, '@api' is replaced with the Parameter object.
65: * - properties: When the type is an object, you can specify nested parameters
66: * - additionalProperties: (array) This attribute defines a schema for all properties that are not explicitly
67: * defined in an object type definition. If specified, the value MUST be a schema or a boolean. If
68: * false is provided, no additional properties are allowed beyond the properties defined in the
69: * schema. The default value is an empty schema which allows any value for additional properties.
70: * - items: This attribute defines the allowed items in an instance array, and MUST be a schema or an array
71: * of schemas. The default value is an empty schema which allows any value for items in the
72: * instance array.
73: * When this attribute value is a schema and the instance value is an array, then all the items
74: * in the array MUST be valid according to the schema.
75: * - pattern: When the type is a string, you can specify the regex pattern that a value must match
76: * - enum: When the type is a string, you can specify a list of acceptable values
77: * - minItems: (int) Minimum number of items allowed in an array
78: * - maxItems: (int) Maximum number of items allowed in an array
79: * - minLength: (int) Minimum length of a string
80: * - maxLength: (int) Maximum length of a string
81: * - minimum: (int) Minimum value of an integer
82: * - maximum: (int) Maximum value of an integer
83: * - data: (array) Any additional custom data to use when serializing, validating, etc
84: * - format: (string) Format used to coax a value into the correct format when serializing or unserializing.
85: * You may specify either an array of filters OR a format, but not both.
86: * Supported values: date-time, date, time, timestamp, date-time-http
87: * - $ref: (string) String referencing a service description model. The parameter is replaced by the
88: * schema contained in the model.
89: *
90: * @param array $data Array of data as seen in service descriptions
91: * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found
92: *
93: * @throws InvalidArgumentException
94: */
95: public function __construct(array $data = array(), ServiceDescriptionInterface $description = null)
96: {
97: if ($description) {
98: if (isset($data['$ref'])) {
99: if ($model = $description->getModel($data['$ref'])) {
100: // The name of the original parameter should override the ref name if one is available
101: $name = empty($data['name']) ? null : $data['name'];
102: $data = $model->toArray();
103: if ($name) {
104: $data['name'] = $name;
105: }
106: }
107: } elseif (isset($data['extends'])) {
108: // If this parameter extends from another parameter then start with the actual data
109: // union in the parent's data (e.g. actual supersedes parent)
110: if ($extends = $description->getModel($data['extends'])) {
111: $data += $extends->toArray();
112: }
113: }
114: }
115:
116: // Pull configuration data into the parameter
117: foreach ($data as $key => $value) {
118: $this->{$key} = $value;
119: }
120:
121: $this->serviceDescription = $description;
122: $this->required = (bool) $this->required;
123: $this->data = (array) $this->data;
124:
125: if ($this->filters) {
126: $this->setFilters((array) $this->filters);
127: }
128:
129: if ($this->type == 'object' && $this->additionalProperties === null) {
130: $this->additionalProperties = true;
131: }
132: }
133:
134: /**
135: * Convert the object to an array
136: *
137: * @return array
138: */
139: public function toArray()
140: {
141: $result = array();
142: $checks = array('required', 'description', 'static', 'type', 'format', 'instanceOf', 'location', 'sentAs',
143: 'pattern', 'minimum', 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum',
144: 'filters');
145:
146: // Anything that is in the `Items` attribute of an array *must* include it's name if available
147: if ($this->parent instanceof self && $this->parent->getType() == 'array' && isset($this->name)) {
148: $result['name'] = $this->name;
149: }
150:
151: foreach ($checks as $c) {
152: if ($value = $this->{$c}) {
153: $result[$c] = $value;
154: }
155: }
156:
157: if ($this->default !== null) {
158: $result['default'] = $this->default;
159: }
160:
161: if ($this->items !== null) {
162: $result['items'] = $this->getItems()->toArray();
163: }
164:
165: if ($this->additionalProperties !== null) {
166: $result['additionalProperties'] = $this->getAdditionalProperties();
167: if ($result['additionalProperties'] instanceof self) {
168: $result['additionalProperties'] = $result['additionalProperties']->toArray();
169: }
170: }
171:
172: if ($this->type == 'object' && $this->properties) {
173: $result['properties'] = array();
174: foreach ($this->getProperties() as $name => $property) {
175: $result['properties'][$name] = $property->toArray();
176: }
177: }
178:
179: return $result;
180: }
181:
182: /**
183: * Get the default or static value of the command based on a value
184: *
185: * @param string $value Value that is currently set
186: *
187: * @return mixed Returns the value, a static value if one is present, or a default value
188: */
189: public function getValue($value)
190: {
191: return $this->static || ($this->default !== null && !$value && ($this->type != 'boolean' || $value !== false))
192: ? $this->default
193: : $value;
194: }
195:
196: /**
197: * Run a value through the filters OR format attribute associated with the parameter
198: *
199: * @param mixed $value Value to filter
200: *
201: * @return mixed Returns the filtered value
202: */
203: public function filter($value)
204: {
205: // Formats are applied exclusively and supersed filters
206: if ($this->format) {
207: return SchemaFormatter::format($this->format, $value);
208: }
209:
210: // Convert Boolean values
211: if ($this->type == 'boolean' && !is_bool($value)) {
212: $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
213: }
214:
215: // Apply filters to the value
216: if ($this->filters) {
217: foreach ($this->filters as $filter) {
218: if (is_array($filter)) {
219: // Convert complex filters that hold value place holders
220: foreach ($filter['args'] as &$data) {
221: if ($data == '@value') {
222: $data = $value;
223: } elseif ($data == '@api') {
224: $data = $this;
225: }
226: }
227: $value = call_user_func_array($filter['method'], $filter['args']);
228: } else {
229: $value = call_user_func($filter, $value);
230: }
231: }
232: }
233:
234: return $value;
235: }
236:
237: /**
238: * Get the name of the parameter
239: *
240: * @return string
241: */
242: public function getName()
243: {
244: return $this->name;
245: }
246:
247: /**
248: * Get the key of the parameter, where sentAs will supersede name if it is set
249: *
250: * @return string
251: */
252: public function getWireName()
253: {
254: return $this->sentAs ?: $this->name;
255: }
256:
257: /**
258: * Set the name of the parameter
259: *
260: * @param string $name Name to set
261: *
262: * @return self
263: */
264: public function setName($name)
265: {
266: $this->name = $name;
267:
268: return $this;
269: }
270:
271: /**
272: * Get the type(s) of the parameter
273: *
274: * @return string|array
275: */
276: public function getType()
277: {
278: return $this->type;
279: }
280:
281: /**
282: * Set the type(s) of the parameter
283: *
284: * @param string|array $type Type of parameter or array of simple types used in a union
285: *
286: * @return self
287: */
288: public function setType($type)
289: {
290: $this->type = $type;
291:
292: return $this;
293: }
294:
295: /**
296: * Get if the parameter is required
297: *
298: * @return bool
299: */
300: public function getRequired()
301: {
302: return $this->required;
303: }
304:
305: /**
306: * Set if the parameter is required
307: *
308: * @param bool $isRequired Whether or not the parameter is required
309: *
310: * @return self
311: */
312: public function setRequired($isRequired)
313: {
314: $this->required = (bool) $isRequired;
315:
316: return $this;
317: }
318:
319: /**
320: * Get the default value of the parameter
321: *
322: * @return string|null
323: */
324: public function getDefault()
325: {
326: return $this->default;
327: }
328:
329: /**
330: * Set the default value of the parameter
331: *
332: * @param string|null $default Default value to set
333: *
334: * @return self
335: */
336: public function setDefault($default)
337: {
338: $this->default = $default;
339:
340: return $this;
341: }
342:
343: /**
344: * Get the description of the parameter
345: *
346: * @return string|null
347: */
348: public function getDescription()
349: {
350: return $this->description;
351: }
352:
353: /**
354: * Set the description of the parameter
355: *
356: * @param string $description Description
357: *
358: * @return self
359: */
360: public function setDescription($description)
361: {
362: $this->description = $description;
363:
364: return $this;
365: }
366:
367: /**
368: * Get the minimum acceptable value for an integer
369: *
370: * @return int|null
371: */
372: public function getMinimum()
373: {
374: return $this->minimum;
375: }
376:
377: /**
378: * Set the minimum acceptable value for an integer
379: *
380: * @param int|null $min Minimum
381: *
382: * @return self
383: */
384: public function setMinimum($min)
385: {
386: $this->minimum = $min;
387:
388: return $this;
389: }
390:
391: /**
392: * Get the maximum acceptable value for an integer
393: *
394: * @return int|null
395: */
396: public function getMaximum()
397: {
398: return $this->maximum;
399: }
400:
401: /**
402: * Set the maximum acceptable value for an integer
403: *
404: * @param int $max Maximum
405: *
406: * @return self
407: */
408: public function setMaximum($max)
409: {
410: $this->maximum = $max;
411:
412: return $this;
413: }
414:
415: /**
416: * Get the minimum allowed length of a string value
417: *
418: * @return int
419: */
420: public function getMinLength()
421: {
422: return $this->minLength;
423: }
424:
425: /**
426: * Set the minimum allowed length of a string value
427: *
428: * @param int|null $min Minimum
429: *
430: * @return self
431: */
432: public function setMinLength($min)
433: {
434: $this->minLength = $min;
435:
436: return $this;
437: }
438:
439: /**
440: * Get the maximum allowed length of a string value
441: *
442: * @return int|null
443: */
444: public function getMaxLength()
445: {
446: return $this->maxLength;
447: }
448:
449: /**
450: * Set the maximum allowed length of a string value
451: *
452: * @param int $max Maximum length
453: *
454: * @return self
455: */
456: public function setMaxLength($max)
457: {
458: $this->maxLength = $max;
459:
460: return $this;
461: }
462:
463: /**
464: * Get the maximum allowed number of items in an array value
465: *
466: * @return int|null
467: */
468: public function getMaxItems()
469: {
470: return $this->maxItems;
471: }
472:
473: /**
474: * Set the maximum allowed number of items in an array value
475: *
476: * @param int $max Maximum
477: *
478: * @return self
479: */
480: public function setMaxItems($max)
481: {
482: $this->maxItems = $max;
483:
484: return $this;
485: }
486:
487: /**
488: * Get the minimum allowed number of items in an array value
489: *
490: * @return int
491: */
492: public function getMinItems()
493: {
494: return $this->minItems;
495: }
496:
497: /**
498: * Set the minimum allowed number of items in an array value
499: *
500: * @param int|null $min Minimum
501: *
502: * @return self
503: */
504: public function setMinItems($min)
505: {
506: $this->minItems = $min;
507:
508: return $this;
509: }
510:
511: /**
512: * Get the location of the parameter
513: *
514: * @return string|null
515: */
516: public function getLocation()
517: {
518: return $this->location;
519: }
520:
521: /**
522: * Set the location of the parameter
523: *
524: * @param string|null $location Location of the parameter
525: *
526: * @return self
527: */
528: public function setLocation($location)
529: {
530: $this->location = $location;
531:
532: return $this;
533: }
534:
535: /**
536: * Get the sentAs attribute of the parameter that used with locations to sentAs an attribute when it is being
537: * applied to a location.
538: *
539: * @return string|null
540: */
541: public function getSentAs()
542: {
543: return $this->sentAs;
544: }
545:
546: /**
547: * Set the sentAs attribute
548: *
549: * @param string|null $name Name of the value as it is sent over the wire
550: *
551: * @return self
552: */
553: public function setSentAs($name)
554: {
555: $this->sentAs = $name;
556:
557: return $this;
558: }
559:
560: /**
561: * Retrieve a known property from the parameter by name or a data property by name. When not specific name value
562: * is specified, all data properties will be returned.
563: *
564: * @param string|null $name Specify a particular property name to retrieve
565: *
566: * @return array|mixed|null
567: */
568: public function getData($name = null)
569: {
570: if (!$name) {
571: return $this->data;
572: }
573:
574: if (isset($this->data[$name])) {
575: return $this->data[$name];
576: } elseif (isset($this->{$name})) {
577: return $this->{$name};
578: }
579:
580: return null;
581: }
582:
583: /**
584: * Set the extra data properties of the parameter or set a specific extra property
585: *
586: * @param string|array|null $nameOrData The name of a specific extra to set or an array of extras to set
587: * @param mixed|null $data When setting a specific extra property, specify the data to set for it
588: *
589: * @return self
590: */
591: public function setData($nameOrData, $data = null)
592: {
593: if (is_array($nameOrData)) {
594: $this->data = $nameOrData;
595: } else {
596: $this->data[$nameOrData] = $data;
597: }
598:
599: return $this;
600: }
601:
602: /**
603: * Get whether or not the default value can be changed
604: *
605: * @return mixed|null
606: */
607: public function getStatic()
608: {
609: return $this->static;
610: }
611:
612: /**
613: * Set to true if the default value cannot be changed
614: *
615: * @param bool $static True or false
616: *
617: * @return self
618: */
619: public function setStatic($static)
620: {
621: $this->static = (bool) $static;
622:
623: return $this;
624: }
625:
626: /**
627: * Get an array of filters used by the parameter
628: *
629: * @return array
630: */
631: public function getFilters()
632: {
633: return $this->filters ?: array();
634: }
635:
636: /**
637: * Set the array of filters used by the parameter
638: *
639: * @param array $filters Array of functions to use as filters
640: *
641: * @return self
642: */
643: public function setFilters(array $filters)
644: {
645: $this->filters = array();
646: foreach ($filters as $filter) {
647: $this->addFilter($filter);
648: }
649:
650: return $this;
651: }
652:
653: /**
654: * Add a filter to the parameter
655: *
656: * @param string|array $filter Method to filter the value through
657: *
658: * @return self
659: * @throws InvalidArgumentException
660: */
661: public function addFilter($filter)
662: {
663: if (is_array($filter)) {
664: if (!isset($filter['method'])) {
665: throw new InvalidArgumentException('A [method] value must be specified for each complex filter');
666: }
667: }
668:
669: if (!$this->filters) {
670: $this->filters = array($filter);
671: } else {
672: $this->filters[] = $filter;
673: }
674:
675: return $this;
676: }
677:
678: /**
679: * Get the parent object (an {@see OperationInterface} or {@see Parameter}
680: *
681: * @return OperationInterface|Parameter|null
682: */
683: public function getParent()
684: {
685: return $this->parent;
686: }
687:
688: /**
689: * Set the parent object of the parameter
690: *
691: * @param OperationInterface|Parameter|null $parent Parent container of the parameter
692: *
693: * @return self
694: */
695: public function setParent($parent)
696: {
697: $this->parent = $parent;
698:
699: return $this;
700: }
701:
702: /**
703: * Get the properties of the parameter
704: *
705: * @return array
706: */
707: public function getProperties()
708: {
709: if (!$this->propertiesCache) {
710: $this->propertiesCache = array();
711: foreach (array_keys($this->properties) as $name) {
712: $this->propertiesCache[$name] = $this->getProperty($name);
713: }
714: }
715:
716: return $this->propertiesCache;
717: }
718:
719: /**
720: * Get a specific property from the parameter
721: *
722: * @param string $name Name of the property to retrieve
723: *
724: * @return null|Parameter
725: */
726: public function getProperty($name)
727: {
728: if (!isset($this->properties[$name])) {
729: return null;
730: }
731:
732: if (!($this->properties[$name] instanceof self)) {
733: $this->properties[$name]['name'] = $name;
734: $this->properties[$name] = new static($this->properties[$name], $this->serviceDescription);
735: $this->properties[$name]->setParent($this);
736: }
737:
738: return $this->properties[$name];
739: }
740:
741: /**
742: * Remove a property from the parameter
743: *
744: * @param string $name Name of the property to remove
745: *
746: * @return self
747: */
748: public function removeProperty($name)
749: {
750: unset($this->properties[$name]);
751: $this->propertiesCache = null;
752:
753: return $this;
754: }
755:
756: /**
757: * Add a property to the parameter
758: *
759: * @param Parameter $property Properties to set
760: *
761: * @return self
762: */
763: public function addProperty(Parameter $property)
764: {
765: $this->properties[$property->getName()] = $property;
766: $property->setParent($this);
767: $this->propertiesCache = null;
768:
769: return $this;
770: }
771:
772: /**
773: * Get the additionalProperties value of the parameter
774: *
775: * @return bool|Parameter|null
776: */
777: public function getAdditionalProperties()
778: {
779: if (is_array($this->additionalProperties)) {
780: $this->additionalProperties = new static($this->additionalProperties, $this->serviceDescription);
781: $this->additionalProperties->setParent($this);
782: }
783:
784: return $this->additionalProperties;
785: }
786:
787: /**
788: * Set the additionalProperties value of the parameter
789: *
790: * @param bool|Parameter|null $additional Boolean to allow any, an Parameter to specify a schema, or false to disallow
791: *
792: * @return self
793: */
794: public function setAdditionalProperties($additional)
795: {
796: $this->additionalProperties = $additional;
797:
798: return $this;
799: }
800:
801: /**
802: * Set the items data of the parameter
803: *
804: * @param Parameter|null $items Items to set
805: *
806: * @return self
807: */
808: public function setItems(Parameter $items = null)
809: {
810: if ($this->items = $items) {
811: $this->items->setParent($this);
812: }
813:
814: return $this;
815: }
816:
817: /**
818: * Get the item data of the parameter
819: *
820: * @return Parameter|null
821: */
822: public function getItems()
823: {
824: if (is_array($this->items)) {
825: $this->items = new static($this->items, $this->serviceDescription);
826: $this->items->setParent($this);
827: }
828:
829: return $this->items;
830: }
831:
832: /**
833: * Get the class that the parameter must implement
834: *
835: * @return null|string
836: */
837: public function getInstanceOf()
838: {
839: return $this->instanceOf;
840: }
841:
842: /**
843: * Set the class that the parameter must be an instance of
844: *
845: * @param string|null $instanceOf Class or interface name
846: *
847: * @return self
848: */
849: public function setInstanceOf($instanceOf)
850: {
851: $this->instanceOf = $instanceOf;
852:
853: return $this;
854: }
855:
856: /**
857: * Get the enum of strings that are valid for the parameter
858: *
859: * @return array|null
860: */
861: public function getEnum()
862: {
863: return $this->enum;
864: }
865:
866: /**
867: * Set the enum of strings that are valid for the parameter
868: *
869: * @param array|null $enum Array of strings or null
870: *
871: * @return self
872: */
873: public function setEnum(array $enum = null)
874: {
875: $this->enum = $enum;
876:
877: return $this;
878: }
879:
880: /**
881: * Get the regex pattern that must match a value when the value is a string
882: *
883: * @return string
884: */
885: public function getPattern()
886: {
887: return $this->pattern;
888: }
889:
890: /**
891: * Set the regex pattern that must match a value when the value is a string
892: *
893: * @param string $pattern Regex pattern
894: *
895: * @return self
896: */
897: public function setPattern($pattern)
898: {
899: $this->pattern = $pattern;
900:
901: return $this;
902: }
903:
904: /**
905: * Get the format attribute of the schema
906: *
907: * @return string
908: */
909: public function getFormat()
910: {
911: return $this->format;
912: }
913:
914: /**
915: * Set the format attribute of the schema
916: *
917: * @param string $format Format to set (e.g. date, date-time, timestamp, time, date-time-http)
918: *
919: * @return self
920: */
921: public function setFormat($format)
922: {
923: $this->format = $format;
924:
925: return $this;
926: }
927: }
928: