PingppObject.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. namespace Pingpp;
  3. use ArrayAccess;
  4. use InvalidArgumentException;
  5. class PingppObject implements ArrayAccess, JsonSerializable
  6. {
  7. /**
  8. * @var array Attributes that should not be sent to the API because they're
  9. * not updatable (e.g. API key, ID).
  10. */
  11. public static $permanentAttributes;
  12. /**
  13. * @var array Attributes that are nested but still updatable from the parent
  14. * class's URL (e.g. metadata).
  15. */
  16. public static $nestedUpdatableAttributes;
  17. public static function init()
  18. {
  19. self::$permanentAttributes = new Util\Set(array('_opts', 'id'));
  20. self::$nestedUpdatableAttributes = new Util\Set(array());
  21. }
  22. protected $_opts;
  23. protected $_values;
  24. protected $_unsavedValues;
  25. protected $_transientValues;
  26. protected $_retrieveOptions;
  27. public function __construct($id = null, $opts = null)
  28. {
  29. $this->_opts = $opts ? $opts : new Util\RequestOptions();
  30. $this->_values = array();
  31. $this->_unsavedValues = new Util\Set();
  32. $this->_transientValues = new Util\Set();
  33. $this->_retrieveOptions = array();
  34. if (is_array($id)) {
  35. foreach ($id as $key => $value) {
  36. if ($key != 'id')
  37. $this->_retrieveOptions[$key] = $value;
  38. }
  39. $id = $id['id'];
  40. }
  41. if ($id)
  42. $this->id = $id;
  43. }
  44. // Standard accessor magic methods
  45. public function __set($k, $v)
  46. {
  47. if ($v === "") {
  48. throw new InvalidArgumentException(
  49. 'You cannot set \''.$k.'\'to an empty string. '
  50. .'We interpret empty strings as NULL in requests. '
  51. .'You may set obj->'.$k.' = NULL to delete the property'
  52. );
  53. }
  54. if (self::$nestedUpdatableAttributes->includes($k) && isset($this->$k) && is_array($v)) {
  55. $this->$k->replaceWith($v);
  56. } else {
  57. // TODO: may want to clear from $_transientValues. (Won't be user-visible.)
  58. $this->_values[$k] = $v;
  59. }
  60. if (!self::$permanentAttributes->includes($k))
  61. $this->_unsavedValues->add($k);
  62. }
  63. public function __isset($k)
  64. {
  65. return isset($this->_values[$k]);
  66. }
  67. public function __unset($k)
  68. {
  69. unset($this->_values[$k]);
  70. $this->_transientValues->add($k);
  71. $this->_unsavedValues->discard($k);
  72. }
  73. public function __get($k)
  74. {
  75. if (array_key_exists($k, $this->_values)) {
  76. return $this->_values[$k];
  77. } else if ($this->_transientValues->includes($k)) {
  78. $class = get_class($this);
  79. $attrs = join(', ', array_keys($this->_values));
  80. $message = "Pingpp Notice: Undefined property of $class instance: $k. "
  81. . "HINT: The $k attribute was set in the past, however. "
  82. . "It was then wiped when refreshing the object "
  83. . "with the result returned by Pingpp's API, "
  84. . "probably as a result of a save(). The attributes currently "
  85. . "available on this object are: $attrs";
  86. error_log($message);
  87. return null;
  88. } else {
  89. $class = get_class($this);
  90. error_log("Pingpp Notice: Undefined property of $class instance: $k");
  91. return null;
  92. }
  93. }
  94. // ArrayAccess methods
  95. public function offsetSet($k, $v)
  96. {
  97. $this->$k = $v;
  98. }
  99. public function offsetExists($k)
  100. {
  101. return array_key_exists($k, $this->_values);
  102. }
  103. public function offsetUnset($k)
  104. {
  105. unset($this->$k);
  106. }
  107. public function offsetGet($k)
  108. {
  109. return array_key_exists($k, $this->_values) ? $this->_values[$k] : null;
  110. }
  111. public function keys()
  112. {
  113. return array_keys($this->_values);
  114. }
  115. /**
  116. * This unfortunately needs to be public to be used in Util.php
  117. *
  118. * @param stdObject $values
  119. * @param array $opts
  120. *
  121. * @return PingppObject The object constructed from the given values.
  122. */
  123. public static function constructFrom($values, $opts)
  124. {
  125. $obj = new static(isset($values->id) ? $values->id : null);
  126. $obj->refreshFrom($values, $opts);
  127. return $obj;
  128. }
  129. /**
  130. * Refreshes this object using the provided values.
  131. *
  132. * @param stdObject $values
  133. * @param array $opts
  134. * @param boolean $partial Defaults to false.
  135. */
  136. public function refreshFrom($values, $opts, $partial = false)
  137. {
  138. $this->_opts = $opts;
  139. // Wipe old state before setting new. This is useful for e.g. updating a
  140. // customer, where there is no persistent card parameter. Mark those values
  141. // which don't persist as transient
  142. if ($partial)
  143. $removed = new Util\Set();
  144. else
  145. $removed = array_diff(array_keys($this->_values), array_keys(get_object_vars($values)));
  146. foreach ($removed as $k) {
  147. if (self::$permanentAttributes->includes($k))
  148. continue;
  149. unset($this->$k);
  150. }
  151. foreach ($values as $k => $v) {
  152. if (self::$permanentAttributes->includes($k))
  153. continue;
  154. if (self::$nestedUpdatableAttributes->includes($k) && is_object($v))
  155. $this->_values[$k] = AttachedObject::constructFrom($v, $opts);
  156. else
  157. $this->_values[$k] = Util\Util::convertToPingppObject($v, $opts);
  158. $this->_transientValues->discard($k);
  159. $this->_unsavedValues->discard($k);
  160. }
  161. }
  162. /**
  163. * @return array A recursive mapping of attributes to values for this object,
  164. * including the proper value for deleted attributes.
  165. */
  166. public function serializeParameters()
  167. {
  168. $params = array();
  169. if ($this->_unsavedValues) {
  170. foreach ($this->_unsavedValues->toArray() as $k) {
  171. $v = $this->$k;
  172. if ($v === NULL) {
  173. $v = '';
  174. }
  175. $params[$k] = $v;
  176. }
  177. }
  178. // Get nested updates.
  179. foreach (self::$nestedUpdatableAttributes->toArray() as $property) {
  180. if (isset($this->$property) && $this->$property instanceOf PingppObject) {
  181. $params[$property] = $this->$property->serializeParameters();
  182. }
  183. }
  184. return $params;
  185. }
  186. public function jsonSerialize()
  187. {
  188. return $this->__toStdObject();
  189. }
  190. public function __toJSON()
  191. {
  192. if (defined('JSON_PRETTY_PRINT')) {
  193. return json_encode($this->__toStdObject(), JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
  194. } else {
  195. return json_encode($this->__toStdObject());
  196. }
  197. }
  198. public function __toString()
  199. {
  200. return $this->__toJSON();
  201. }
  202. public function __toArray($recursive = false)
  203. {
  204. if ($recursive)
  205. return Util\Util::convertPingppObjectToArray($this->_values);
  206. else
  207. return $this->_values;
  208. }
  209. public function __toStdObject()
  210. {
  211. return Util\Util::convertPingppObjectToStdObject($this->_values);
  212. }
  213. }
  214. PingppObject::init();