composer package updates
[openemr.git] / vendor / stripe / stripe-php / lib / StripeObject.php
blob461fa3115cb6007c8a5f40cbb2805b410fc6b943
1 <?php
3 namespace Stripe;
5 /**
6 * Class StripeObject
8 * @package Stripe
9 */
10 class StripeObject implements \ArrayAccess, \Countable, \JsonSerializable
12 protected $_opts;
13 protected $_originalValues;
14 protected $_values;
15 protected $_unsavedValues;
16 protected $_transientValues;
17 protected $_retrieveOptions;
18 protected $_lastResponse;
20 /**
21 * @return Util\Set Attributes that should not be sent to the API because
22 * they're not updatable (e.g. ID).
24 public static function getPermanentAttributes()
26 static $permanentAttributes = null;
27 if ($permanentAttributes === null) {
28 $permanentAttributes = new Util\Set([
29 'id',
30 ]);
32 return $permanentAttributes;
35 /**
36 * Additive objects are subobjects in the API that don't have the same
37 * semantics as most subobjects, which are fully replaced when they're set.
38 * This is best illustrated by example. The `source` parameter sent when
39 * updating a subscription is *not* additive; if we set it:
41 * source[object]=card&source[number]=123
43 * We expect the old `source` object to have been overwritten completely. If
44 * the previous source had an `address_state` key associated with it and we
45 * didn't send one this time, that value of `address_state` is gone.
47 * By contrast, additive objects are those that will have new data added to
48 * them while keeping any existing data in place. The only known case of its
49 * use is for `metadata`, but it could in theory be more general. As an
50 * example, say we have a `metadata` object that looks like this on the
51 * server side:
53 * metadata = ["old" => "old_value"]
55 * If we update the object with `metadata[new]=new_value`, the server side
56 * object now has *both* fields:
58 * metadata = ["old" => "old_value", "new" => "new_value"]
60 * This is okay in itself because usually users will want to treat it as
61 * additive:
63 * $obj->metadata["new"] = "new_value";
64 * $obj->save();
66 * However, in other cases, they may want to replace the entire existing
67 * contents:
69 * $obj->metadata = ["new" => "new_value"];
70 * $obj->save();
72 * This is where things get a little bit tricky because in order to clear
73 * any old keys that may have existed, we actually have to send an explicit
74 * empty string to the server. So the operation above would have to send
75 * this form to get the intended behavior:
77 * metadata[old]=&metadata[new]=new_value
79 * This method allows us to track which parameters are considered additive,
80 * and lets us behave correctly where appropriate when serializing
81 * parameters to be sent.
83 * @return Util\Set Set of additive parameters
85 public static function getAdditiveParams()
87 static $additiveParams = null;
88 if ($additiveParams === null) {
89 // Set `metadata` as additive so that when it's set directly we remember
90 // to clear keys that may have been previously set by sending empty
91 // values for them.
93 // It's possible that not every object has `metadata`, but having this
94 // option set when there is no `metadata` field is not harmful.
95 $additiveParams = new Util\Set([
96 'metadata',
97 ]);
99 return $additiveParams;
102 public function __construct($id = null, $opts = null)
104 list($id, $this->_retrieveOptions) = Util\Util::normalizeId($id);
105 $this->_opts = Util\RequestOptions::parse($opts);
106 $this->_originalValues = [];
107 $this->_values = [];
108 $this->_unsavedValues = new Util\Set();
109 $this->_transientValues = new Util\Set();
110 if ($id !== null) {
111 $this->_values['id'] = $id;
115 // Standard accessor magic methods
116 public function __set($k, $v)
118 if (static::getPermanentAttributes()->includes($k)) {
119 throw new \InvalidArgumentException(
120 "Cannot set $k on this object. HINT: you can't set: " .
121 join(', ', static::getPermanentAttributes()->toArray())
125 if ($v === "") {
126 throw new \InvalidArgumentException(
127 'You cannot set \''.$k.'\'to an empty string. '
128 .'We interpret empty strings as NULL in requests. '
129 .'You may set obj->'.$k.' = NULL to delete the property'
133 $this->_values[$k] = Util\Util::convertToStripeObject($v, $this->_opts);
134 $this->dirtyValue($this->_values[$k]);
135 $this->_unsavedValues->add($k);
138 public function __isset($k)
140 return isset($this->_values[$k]);
143 public function __unset($k)
145 unset($this->_values[$k]);
146 $this->_transientValues->add($k);
147 $this->_unsavedValues->discard($k);
150 public function &__get($k)
152 // function should return a reference, using $nullval to return a reference to null
153 $nullval = null;
154 if (!empty($this->_values) && array_key_exists($k, $this->_values)) {
155 return $this->_values[$k];
156 } else if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) {
157 $class = get_class($this);
158 $attrs = join(', ', array_keys($this->_values));
159 $message = "Stripe Notice: Undefined property of $class instance: $k. "
160 . "HINT: The $k attribute was set in the past, however. "
161 . "It was then wiped when refreshing the object "
162 . "with the result returned by Stripe's API, "
163 . "probably as a result of a save(). The attributes currently "
164 . "available on this object are: $attrs";
165 Stripe::getLogger()->error($message);
166 return $nullval;
167 } else {
168 $class = get_class($this);
169 Stripe::getLogger()->error("Stripe Notice: Undefined property of $class instance: $k");
170 return $nullval;
174 // Magic method for var_dump output. Only works with PHP >= 5.6
175 public function __debugInfo()
177 return $this->_values;
180 // ArrayAccess methods
181 public function offsetSet($k, $v)
183 $this->$k = $v;
186 public function offsetExists($k)
188 return array_key_exists($k, $this->_values);
191 public function offsetUnset($k)
193 unset($this->$k);
196 public function offsetGet($k)
198 return array_key_exists($k, $this->_values) ? $this->_values[$k] : null;
201 // Countable method
202 public function count()
204 return count($this->_values);
207 public function keys()
209 return array_keys($this->_values);
212 public function values()
214 return array_values($this->_values);
218 * This unfortunately needs to be public to be used in Util\Util
220 * @param array $values
221 * @param null|string|array|Util\RequestOptions $opts
223 * @return StripeObject The object constructed from the given values.
225 public static function constructFrom($values, $opts = null)
227 $obj = new static(isset($values['id']) ? $values['id'] : null);
228 $obj->refreshFrom($values, $opts);
229 return $obj;
233 * Refreshes this object using the provided values.
235 * @param array $values
236 * @param null|string|array|Util\RequestOptions $opts
237 * @param boolean $partial Defaults to false.
239 public function refreshFrom($values, $opts, $partial = false)
241 $this->_opts = Util\RequestOptions::parse($opts);
243 $this->_originalValues = self::deepCopy($values);
245 if ($values instanceof StripeObject) {
246 $values = $values->__toArray(true);
249 // Wipe old state before setting new. This is useful for e.g. updating a
250 // customer, where there is no persistent card parameter. Mark those values
251 // which don't persist as transient
252 if ($partial) {
253 $removed = new Util\Set();
254 } else {
255 $removed = new Util\Set(array_diff(array_keys($this->_values), array_keys($values)));
258 foreach ($removed->toArray() as $k) {
259 unset($this->$k);
262 $this->updateAttributes($values, $opts, false);
263 foreach ($values as $k => $v) {
264 $this->_transientValues->discard($k);
265 $this->_unsavedValues->discard($k);
270 * Mass assigns attributes on the model.
272 * @param array $values
273 * @param null|string|array|Util\RequestOptions $opts
274 * @param boolean $dirty Defaults to true.
276 public function updateAttributes($values, $opts = null, $dirty = true)
278 foreach ($values as $k => $v) {
279 // Special-case metadata to always be cast as a StripeObject
280 // This is necessary in case metadata is empty, as PHP arrays do
281 // not differentiate between lists and hashes, and we consider
282 // empty arrays to be lists.
283 if ($k === "metadata") {
284 $this->_values[$k] = StripeObject::constructFrom($v, $opts);
285 } else {
286 $this->_values[$k] = Util\Util::convertToStripeObject($v, $opts);
288 if ($dirty) {
289 $this->dirtyValue($this->_values[$k]);
291 $this->_unsavedValues->add($k);
296 * @return array A recursive mapping of attributes to values for this object,
297 * including the proper value for deleted attributes.
299 public function serializeParameters($force = false)
301 $updateParams = [];
303 foreach ($this->_values as $k => $v) {
304 // There are a few reasons that we may want to add in a parameter for
305 // update:
307 // 1. The `$force` option has been set.
308 // 2. We know that it was modified.
309 // 3. Its value is a StripeObject. A StripeObject may contain modified
310 // values within in that its parent StripeObject doesn't know about.
312 $original = array_key_exists($k, $this->_originalValues) ? $this->_originalValues[$k] : null;
313 $unsaved = $this->_unsavedValues->includes($k);
314 if ($force || $unsaved || $v instanceof StripeObject) {
315 $updateParams[$k] = $this->serializeParamsValue(
316 $this->_values[$k],
317 $original,
318 $unsaved,
319 $force,
325 // a `null` that makes it out of `serializeParamsValue` signals an empty
326 // value that we shouldn't appear in the serialized form of the object
327 $updateParams = array_filter(
328 $updateParams,
329 function ($v) {
330 return $v !== null;
334 return $updateParams;
338 public function serializeParamsValue($value, $original, $unsaved, $force, $key = null)
340 // The logic here is that essentially any object embedded in another
341 // object that had a `type` is actually an API resource of a different
342 // type that's been included in the response. These other resources must
343 // be updated from their proper endpoints, and therefore they are not
344 // included when serializing even if they've been modified.
346 // There are _some_ known exceptions though.
348 // For example, if the value is unsaved (meaning the user has set it), and
349 // it looks like the API resource is persisted with an ID, then we include
350 // the object so that parameters are serialized with a reference to its
351 // ID.
353 // Another example is that on save API calls it's sometimes desirable to
354 // update a customer's default source by setting a new card (or other)
355 // object with `->source=` and then saving the customer. The
356 // `saveWithParent` flag to override the default behavior allows us to
357 // handle these exceptions.
359 // We throw an error if a property was set explicitly but we can't do
360 // anything with it because the integration is probably not working as the
361 // user intended it to.
362 if ($value === null) {
363 return "";
364 } elseif (($value instanceof APIResource) && (!$value->saveWithParent)) {
365 if (!$unsaved) {
366 return null;
367 } elseif (isset($value->id)) {
368 return $value;
369 } else {
370 throw new \InvalidArgumentException(
371 "Cannot save property `$key` containing an API resource of type " .
372 get_class($value) . ". It doesn't appear to be persisted and is " .
373 "not marked as `saveWithParent`."
376 } elseif (is_array($value)) {
377 if (Util\Util::isList($value)) {
378 // Sequential array, i.e. a list
379 $update = [];
380 foreach ($value as $v) {
381 array_push($update, $this->serializeParamsValue($v, null, true, $force));
383 // This prevents an array that's unchanged from being resent.
384 if ($update !== $this->serializeParamsValue($original, null, true, $force, $key)) {
385 return $update;
387 } else {
388 // Associative array, i.e. a map
389 return Util\Util::convertToStripeObject($value, $this->_opts)->serializeParameters();
391 } elseif ($value instanceof StripeObject) {
392 $update = $value->serializeParameters($force);
393 if ($original && $unsaved && $key && static::getAdditiveParams()->includes($key)) {
394 $update = array_merge(self::emptyValues($original), $update);
396 return $update;
397 } else {
398 return $value;
402 public function jsonSerialize()
404 return $this->__toArray(true);
407 public function __toJSON()
409 return json_encode($this->__toArray(true), JSON_PRETTY_PRINT);
412 public function __toString()
414 $class = get_class($this);
415 return $class . ' JSON: ' . $this->__toJSON();
418 public function __toArray($recursive = false)
420 if ($recursive) {
421 return Util\Util::convertStripeObjectToArray($this->_values);
422 } else {
423 return $this->_values;
428 * Sets all keys within the StripeObject as unsaved so that they will be
429 * included with an update when `serializeParameters` is called. This
430 * method is also recursive, so any StripeObjects contained as values or
431 * which are values in a tenant array are also marked as dirty.
433 public function dirty()
435 $this->_unsavedValues = new Util\Set(array_keys($this->_values));
436 foreach ($this->_values as $k => $v) {
437 $this->dirtyValue($v);
441 protected function dirtyValue($value)
443 if (is_array($value)) {
444 foreach ($value as $v) {
445 $this->dirtyValue($v);
447 } elseif ($value instanceof StripeObject) {
448 $value->dirty();
453 * Produces a deep copy of the given object including support for arrays
454 * and StripeObjects.
456 protected static function deepCopy($obj)
458 if (is_array($obj)) {
459 $copy = [];
460 foreach ($obj as $k => $v) {
461 $copy[$k] = self::deepCopy($v);
463 return $copy;
464 } elseif ($obj instanceof StripeObject) {
465 return $obj::constructFrom(
466 self::deepCopy($obj->_values),
467 clone $obj->_opts
469 } else {
470 return $obj;
475 * Returns a hash of empty values for all the values that are in the given
476 * StripeObject.
478 public static function emptyValues($obj)
480 if (is_array($obj)) {
481 $values = $obj;
482 } elseif ($obj instanceof StripeObject) {
483 $values = $obj->_values;
484 } else {
485 throw new \InvalidArgumentException(
486 "empty_values got got unexpected object type: " . get_class($obj)
489 $update = array_fill_keys(array_keys($values), "");
490 return $update;
494 * @return object The last response from the Stripe API
496 public function getLastResponse()
498 return $this->_lastResponse;
502 * @param ApiResponse
504 * @return void Set the last response from the Stripe API
506 public function setLastResponse($resp)
508 $this->_lastResponse = $resp;