1: <?php
2: /**
3: * Partner API Library
4: *
5: * @copyright Copyright (c) 2020 Asseco Data Systems SA
6: * @license license.txt
7: */
8:
9: /**
10: * This is a base class for implementations of WSDL types.
11: *
12: * This class contains some common properties and methods for all types.
13: * It also implements "magic methods" like __call() and __get() to access
14: * WSDL elements.
15: * When overridden, the new class must implement the initData() method which
16: * should return an array containing all type elements.
17: *
18: * @package types
19: */
20: abstract class PartnerAPIType {
21:
22: /**
23: * This is an array containing all elements in a given type.
24: *
25: * The keys in this array are names of WSDL elements and values are
26: * arrays contaings the following keys and values:
27: * 'min' = 0 if attribute 'minOccurs' = 0 or 1 otherwise,
28: * 'max' = NULL if attribute maxOccurs = "unbounded" or 1 otherwise,
29: * 'value' = NULL if 'min' = 0 or a simple value of element's type, e.g. "" for string, 0 for int and long, FALSE for boolean and new "object" for a class,
30: * 'type' = name of element's type, it can be 'string', 'int', 'long', 'boolean' or a class name which must be derived from PartnerAPIType,
31: * 'nillable' => TRUE if attribute nillable = "true", or FALSE otherwise
32: *
33: * @var array
34: */
35: protected $elems = array();
36:
37: /* *
38: * This is an array containing choices from a WSDL file.
39: *
40: * Every array's element is an array representing one choice from WSDL.
41: * Every choice is an array containg all the choice options where each
42: * element is an array containing a set of WSDL elements being an option.
43: * An example:
44: * <pre>
45: *
46: * <xs:choice>
47: * <xs:element name="id" type="xs:long"/>
48: * <xs:sequence>
49: * <xs:element name="name" type="xs:string"/>
50: * <xs:element name="description" type="xs:string"/>
51: * </xs:sequence>
52: * </xs:choice>
53: *
54: * $choices = array(
55: * 0 => array(
56: * 0 => array("id"),
57: * 1 => array("name", "description")
58: * )
59: * );
60: * </pre>
61: * All the array keys are optional and arbitrary.
62: *
63: * @var array
64: */
65: //protected $choices = array();
66:
67: /**
68: * This is an abstract method used to initialize type elements.
69: *
70: * When overriden, it must return an array containing all elements for a given type.
71: * The structure of this array is defined in the description of $elems variable.
72: * If a type is deriving from another type derived from PartnerAPIType
73: * then the implementations of this method must call the parent's initData() method
74: * and merge all elements.
75: *
76: * @return array A set of all type elements
77: */
78: abstract protected function initData();
79:
80: /**
81: * It is the constructor.
82: *
83: * It just initialize data.
84: */
85: public function __construct() {
86: $this->resetData();
87: }
88:
89: /**
90: * This method resets object's data.
91: *
92: * It sets all elements to initial states.
93: */
94: public function resetData() {
95: $this->elems = $this->initData();
96: }
97:
98: /**
99: * This method sets values of contained elements
100: *
101: * The structure of the data argument must be the same as the structure of
102: * data returned when calling an operation on an object of SoapClient class.
103: *
104: * This method, although public, is not intended to be called directly.
105: * It is rather used internally.
106: *
107: * @param array $data Data to be set as elements' values
108: * @return PartnerAPIType
109: * @throws PartnerAPIException
110: */
111: public function setData($data) {
112: foreach ($data as $elemName => $value) {
113: if (isset($this->elems[$elemName])) {
114: //if (! $this->canBeChoiced($elemName)) {
115: // require_once 'certumPartnerAPI/exceptions/exceptions.php';
116: // throw new PartnerAPIException("Element '$elemName' cannot be set. It is an option in a choice structure and another option has already been chosen.");
117: //}
118: $props = $this->elems[$elemName];
119: if (! is_array($value))
120: $value = array($value);
121: else if (! is_null($props['max']) && count($value) > $props['max']) {
122: require_once 'certumPartnerAPI/exceptions/exceptions.php';
123: throw new PartnerAPIException("Unexpected behavior. Too many (".count($value).") values for element $elemName returned from a service. The element can hold ".$props['max']." values.");
124: }
125: foreach ($value as $v) {
126: $arg = array();
127: if (in_array($props['type'], array('string', 'int', 'long', 'boolean'))) {
128: $arg = array($v);
129: } else {
130: $newElement = new $props['type'];
131: $newElement->setData($v);
132: $arg = array($newElement);
133: }
134: $this->addElement($elemName, $arg);
135: }
136: }
137: }
138: return $this;
139: }
140:
141: /**
142: * This method return all elements and their values as an array
143: *
144: * It builds a nested array of arrays or simple values depending on
145: * the structure of a type definition.
146: * Simple values are string, int, long, boolean and null.
147: * Each key is an element's name.
148: * A value can be a simple value or an array if an element's value is
149: * an array or an object.
150: *
151: * The argument $omitNullValues tells if elements which value is NULL
152: * will be omitted.
153: *
154: * @param boolean $omitNullValues
155: * @return array A set of all elements and they values
156: */
157: public function getDataAsArray($omitNullValues = FALSE) {
158: $r = array();
159: foreach ($this->elems as $elem => $props) {
160: if ($omitNullValues && is_null($props['value']))
161: continue;
162: $value = NULL;
163: if (is_null($props['max']) && is_array($props['value'])) {
164: $value = array();
165: foreach ($props['value'] as $v)
166: $value[] = is_object($v) ? $v->getDataAsArray() : $v;
167: } else
168: $value = is_object($props['value']) ? $props['value']->getDataAsArray() : $props['value'];
169: $r[$elem] = $value;
170: }
171: return $r;
172: }
173:
174: // ====================================================== magic methods
175: // ====================================================== call, get, set
176:
177: /**
178: * This is a "magic" method invoked when an inaccessible method is called
179: *
180: * This method supports three kinds of calls:
181: * - a setting method which name must be formed like setXxx
182: * - a adding method which name must be formed like addXxx
183: * - a getting method which name must be formed like getXxx
184: * and the Xxx part of a method's name is an element's name.
185: *
186: * When invoking a setXxx or addXxx method it gets one argument which is
187: * passed to this method in an array as the second argument $arguments.
188: * The passed arguments must be null, string, int, long, boolean or an object
189: * of a type derived from PartnerAPIType, depending on a type's WSDL definition.
190: *
191: * @param string $name A name of invoked method
192: * @param array $arguments An array with a value to be set
193: * @return PartnerAPIType
194: * @throws PartnerAPIException
195: */
196: public function __call($name, $arguments) {
197: if (strlen($name) <= 3) {
198: require_once 'certumPartnerAPI/exceptions/exceptions.php';
199: throw new PartnerAPIException("Invalid method name '$name' for this object.");
200: }
201: $methodType = substr($name, 0, 3);
202: if (!in_array($methodType, array('set', 'add', 'get'))) {
203: require_once 'certumPartnerAPI/exceptions/exceptions.php';
204: throw new PartnerAPIException("Invalid method name '$name' for this object.");
205: }
206: $element = $this->findElemName(substr($name, 3));
207: if (is_null($element)) {
208: require_once 'certumPartnerAPI/exceptions/exceptions.php';
209: throw new PartnerAPIException("Invalid method name '$name' for this object.");
210: }
211: if ($methodType == 'set')
212: return $this->setElement($element, $arguments);
213: if ($methodType == 'add')
214: return $this->addElement($element, $arguments);
215: if ($methodType == 'get')
216: return $this->getElement($element);
217: require_once 'certumPartnerAPI/exceptions/exceptions.php';
218: throw new PartnerAPIException("An unexpected error occurred.");
219: }
220:
221: /**
222: * Sets an element's value.
223: *
224: * This method is called internally and by the __call() "magic" method.
225: * It is not recommended to invoke it directly.
226: *
227: * This method sets the value of an element. The value is passed in an array
228: * as the second argument $arguments.
229: * The new value replaces the old value.
230: *
231: * @param string $element An element's name
232: * @param array $arguments An array with a value to be set
233: * @return PartnerAPIType
234: * @throws PartnerAPIException
235: */
236: protected function setElement($element, $arguments) {
237: //if (! $this->canBeChoiced($element)) {
238: // require_once 'certumPartnerAPI/exceptions/exceptions.php';
239: // throw new PartnerAPIException("Element '$element' cannot be set. It is an option in a choice structure and another option has already been chosen.");
240: //}
241: $props = $this->elems[$element];
242: $arg = $arguments[0];
243: $value = null;
244: if (is_null($arg)) {
245: if ($props['min'] == 0)
246: $value = null;
247: else if ($props['nillable']) {
248: $value = null;
249: } else {
250: require_once 'certumPartnerAPI/exceptions/exceptions.php';
251: throw new PartnerAPIException("The element '$element' cannot be set with the value of NULL.");
252: }
253: } else {
254: if ($props['type'] == 'string')
255: $value = (string) $arg;
256: else if ($props['type'] == 'int')
257: $value = (int) $arg;
258: else if ($props['type'] == 'long')
259: $value = (int) $arg;
260: else if ($props['type'] == 'boolean')
261: $value = (bool) $arg;
262: else
263: if (is_object($arg) && (get_class($arg) == $props['type']))
264: $value = $arg;
265: else {
266: require_once 'certumPartnerAPI/exceptions/exceptions.php';
267: throw new PartnerAPIException("The element '$element' has to be set with an object of type ".$props['type'].".");
268: }
269: }
270: $this->elems[$element]['value'] = $value;
271: $this->elems[$element]['was_set'] = TRUE;
272: return $this;
273: }
274:
275: /**
276: * Adds a value to an element's set of values.
277: *
278: * This method is called internally and by the __call() "magic" method.
279: * It is not recommended to invoke it directly.
280: *
281: * This method adds the value to the set of an element' values.
282: * The new value is passed in an array as the second argument $arguments.
283: *
284: * This methods can be invoked only for types which have the attribute 'max' set to NULL.
285: * If the 'max' attribute is not NULL then the setElement() method is invoked
286: * and the new value replaces the old value.
287: *
288: * @param string $element An element's name
289: * @param array $arguments An array with a value to be set
290: * @return PartnerAPIType
291: * @throws PartnerAPIException
292: */
293: protected function addElement($element, $arguments) {
294: //if (! $this->canBeChoiced($element)) {
295: // require_once 'certumPartnerAPI/exceptions/exceptions.php';
296: // throw new PartnerAPIException("Element '$element' cannot be set. It is an option in a choice structure and another option has already been chosen.");
297: //}
298: $props = $this->elems[$element];
299: if (! isset($props['was_set']) || FALSE === $props['was_set'])
300: return $this->setElement($element, $arguments);
301: if ($props['max'] == 1)
302: return $this->setElement($element, $arguments);
303: $arg = $arguments[0];
304: $value = null;
305: if ($props['nillable'] || ! (is_null($arg) || is_null($props['value']))) {
306: if (is_null($arg))
307: $value = null;
308: else {
309: if ($props['type'] == 'string')
310: $value = (string) $arg;
311: else if ($props['type'] == 'int')
312: $value = (int) $arg;
313: else if ($props['type'] == 'long')
314: $value = (int) $arg;
315: else if ($props['type'] == 'boolean')
316: $value = (bool) $arg;
317: else
318: if (is_object($arg) && (get_class($arg) == $props['type']))
319: $value = $arg;
320: else {
321: require_once 'certumPartnerAPI/exceptions/exceptions.php';
322: throw new PartnerAPIException("The element '$element' has to be set with an object of type ".$props['type'].".");
323: }
324: }
325: }
326: else if (! $props['nillable'] && is_null($props['value']))
327: return $this->setElement($element, $arguments);
328: else {
329: require_once 'certumPartnerAPI/exceptions/exceptions.php';
330: throw new PartnerAPIException("The element '$element' cannot be set with the value of null.");
331: }
332: if (! is_array($this->elems[$element]['value']))
333: $this->elems[$element]['value'] = array($this->elems[$element]['value']);
334: $this->elems[$element]['value'][] = $value;
335: $this->elems[$element]['was_set'] = TRUE;
336: return $this;
337: }
338:
339: /**
340: * Gets an element's value.
341: *
342: * This method is called by the __call "magic" method.
343: * It is not recommended to invoke it directly.
344: *
345: * It just return the value of an element. It can be null, string, int, long,
346: * boolean or an object.
347: *
348: * @param string $element An element's name
349: * @return string|int|long|boolean|null|object The value of an element
350: */
351: protected function getElement($element) {
352: return $this->elems[$element]['value'];
353: }
354:
355: /**
356: * This is a "magic" method invoked when an inaccessible property is accessed.
357: *
358: * The argument $name is an accessed property and it must be an element's name.
359: *
360: * @param string $name An element's name
361: * @return string|int|long|boolean|null|object The value of an element
362: * @throws PartnerAPIException
363: */
364: public function __get($name) {
365: $element = $this->findElemName($name);
366: if (is_null($element)) {
367: require_once 'certumPartnerAPI/exceptions/exceptions.php';
368: throw new PartnerAPIException("Invalid property name '$name' for this object.");
369: }
370: return $this->getElement($element);
371: }
372:
373: /**
374: * This method converts an element's name to a proper name.
375: *
376: * It tries to find if the given element's name exists.
377: * It checks the name as it has been given, and with the first letter uppercased and lowercased.
378: * If the proper name has been found it is returned, otherwise NULL is returned.
379: *
380: * It is used by "magic" methods, so it is not so important whether an element's name
381: * is given with the first letter uppercased or lowercased. But always try to use it
382: * exactly as it is defined in WSDL file or the documentation for a type.
383: *
384: * @param string $name An element's name
385: * @return string|null An element's name or null
386: */
387: protected function findElemName($name) {
388: if (isset($this->elems[$name]))
389: return $name;
390: $name[0] = strtolower($name[0]);
391: if (isset($this->elems[$name]))
392: return $name;
393: $name[0] = strtoupper($name[0]);
394: if (isset($this->elems[$name]))
395: return $name;
396: return NULL;
397: }
398:
399: /* *
400: * This method determines if an element can be set in case when it belongs to a WSDL choice.
401: *
402: * If the $choices array is populated with choices based on a WSDL file
403: * than it is examined if an element given in the $elemName parameter can be set.
404: * If any element in any other choice's option has already been set
405: * then the given element cannot be set.
406: *
407: * @param string $elemName An element's name
408: * @return bool Determines if an element can be set
409: */
410: /*
411: protected function canBeChoiced($elemName) {
412: $result = TRUE;
413: if (!is_array($this->choices))
414: return $result;
415: foreach ($this->choices as $wsdlChoice) {
416: if (!is_array($wsdlChoice))
417: continue;
418: $choiceHasOtherChoicesUsed = FALSE;
419: $choiceHasElement = FALSE;
420: foreach ($wsdlChoice as $singleChoice) {
421: if (!is_array($singleChoice))
422: continue;
423: $singleChoiceHasElement = FALSE;
424: $singleChoiceIsUsed = FALSE;
425: foreach ($singleChoice as $element) {
426: if ($elemName == $element)
427: $singleChoiceHasElement = TRUE;
428: else
429: if (isset($this->elems[$element]['was_set']) && $this->elems[$element]['was_set'])
430: $singleChoiceIsUsed = TRUE;
431: }
432: if ($singleChoiceHasElement)
433: $singleChoiceIsUsed = FALSE;
434: $choiceHasOtherChoicesUsed = $choiceHasOtherChoicesUsed || $singleChoiceIsUsed;
435: $choiceHasElement = $choiceHasElement || $singleChoiceHasElement;
436: }
437: if ($choiceHasElement && $choiceHasOtherChoicesUsed) {
438: $result = FALSE;
439: break;
440: }
441: }
442: return $result;
443: }
444: */
445: }
446: