PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / vendor / stripe / stripe-php / lib / StripeObject.php

StripeObject.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at vendor/stripe/stripe-php/lib/StripeObject.php

610 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Stripe;
4
5 /**
6 * Class StripeObject.
7 */
8 class StripeObject implements \ArrayAccess, \Countable, \JsonSerializable
9 {
10 /** @var Util\RequestOptions */
11 protected $_opts;
12
13 /** @var array */
14 protected $_originalValues;
15
16 /** @var array */
17 protected $_values;
18
19 /** @var Util\Set */
20 protected $_unsavedValues;
21
22 /** @var Util\Set */
23 protected $_transientValues;
24
25 /** @var null|array */
26 protected $_retrieveOptions;
27
28 /** @var null|ApiResponse */
29 protected $_lastResponse;
30
31 /**
32 * @return Util\Set Attributes that should not be sent to the API because
33 * they're not updatable (e.g. ID).
34 */
35 public static function getPermanentAttributes()
36 {
37 static $permanentAttributes = null;
38 if (null === $permanentAttributes) {
39 $permanentAttributes = new Util\Set([
40 'id',
41 ]);
42 }
43
44 return $permanentAttributes;
45 }
46
47 /**
48 * Additive objects are subobjects in the API that don't have the same
49 * semantics as most subobjects, which are fully replaced when they're set.
50 *
51 * This is best illustrated by example. The `source` parameter sent when
52 * updating a subscription is *not* additive; if we set it:
53 *
54 * source[object]=card&source[number]=123
55 *
56 * We expect the old `source` object to have been overwritten completely. If
57 * the previous source had an `address_state` key associated with it and we
58 * didn't send one this time, that value of `address_state` is gone.
59 *
60 * By contrast, additive objects are those that will have new data added to
61 * them while keeping any existing data in place. The only known case of its
62 * use is for `metadata`, but it could in theory be more general. As an
63 * example, say we have a `metadata` object that looks like this on the
64 * server side:
65 *
66 * metadata = ["old" => "old_value"]
67 *
68 * If we update the object with `metadata[new]=new_value`, the server side
69 * object now has *both* fields:
70 *
71 * metadata = ["old" => "old_value", "new" => "new_value"]
72 *
73 * This is okay in itself because usually users will want to treat it as
74 * additive:
75 *
76 * $obj->metadata["new"] = "new_value";
77 * $obj->save();
78 *
79 * However, in other cases, they may want to replace the entire existing
80 * contents:
81 *
82 * $obj->metadata = ["new" => "new_value"];
83 * $obj->save();
84 *
85 * This is where things get a little bit tricky because in order to clear
86 * any old keys that may have existed, we actually have to send an explicit
87 * empty string to the server. So the operation above would have to send
88 * this form to get the intended behavior:
89 *
90 * metadata[old]=&metadata[new]=new_value
91 *
92 * This method allows us to track which parameters are considered additive,
93 * and lets us behave correctly where appropriate when serializing
94 * parameters to be sent.
95 *
96 * @return Util\Set Set of additive parameters
97 */
98 public static function getAdditiveParams()
99 {
100 static $additiveParams = null;
101 if (null === $additiveParams) {
102 // Set `metadata` as additive so that when it's set directly we remember
103 // to clear keys that may have been previously set by sending empty
104 // values for them.
105 //
106 // It's possible that not every object has `metadata`, but having this
107 // option set when there is no `metadata` field is not harmful.
108 $additiveParams = new Util\Set([
109 'metadata',
110 ]);
111 }
112
113 return $additiveParams;
114 }
115
116 public function __construct($id = null, $opts = null)
117 {
118 list($id, $this->_retrieveOptions) = Util\Util::normalizeId($id);
119 $this->_opts = Util\RequestOptions::parse($opts);
120 $this->_originalValues = [];
121 $this->_values = [];
122 $this->_unsavedValues = new Util\Set();
123 $this->_transientValues = new Util\Set();
124 if (null !== $id) {
125 $this->_values['id'] = $id;
126 }
127 }
128
129 // Standard accessor magic methods
130 public function __set($k, $v)
131 {
132 if (static::getPermanentAttributes()->includes($k)) {
133 throw new Exception\InvalidArgumentException(
134 "Cannot set {$k} on this object. HINT: you can't set: " .
135 \implode(', ', static::getPermanentAttributes()->toArray())
136 );
137 }
138
139 if ('' === $v) {
140 throw new Exception\InvalidArgumentException(
141 'You cannot set \'' . $k . '\'to an empty string. '
142 . 'We interpret empty strings as NULL in requests. '
143 . 'You may set obj->' . $k . ' = NULL to delete the property'
144 );
145 }
146
147 $this->_values[$k] = Util\Util::convertToStripeObject($v, $this->_opts);
148 $this->dirtyValue($this->_values[$k]);
149 $this->_unsavedValues->add($k);
150 }
151
152 /**
153 * @param mixed $k
154 *
155 * @return bool
156 */
157 public function __isset($k)
158 {
159 return isset($this->_values[$k]);
160 }
161
162 public function __unset($k)
163 {
164 unset($this->_values[$k]);
165 $this->_transientValues->add($k);
166 $this->_unsavedValues->discard($k);
167 }
168
169 public function &__get($k)
170 {
171 // function should return a reference, using $nullval to return a reference to null
172 $nullval = null;
173 if (!empty($this->_values) && \array_key_exists($k, $this->_values)) {
174 return $this->_values[$k];
175 }
176 if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) {
177 $class = static::class;
178 $attrs = \implode(', ', \array_keys($this->_values));
179 $message = "Stripe Notice: Undefined property of {$class} instance: {$k}. "
180 . "HINT: The {$k} attribute was set in the past, however. "
181 . 'It was then wiped when refreshing the object '
182 . "with the result returned by Stripe's API, "
183 . 'probably as a result of a save(). The attributes currently '
184 . "available on this object are: {$attrs}";
185 Stripe::getLogger()->error($message);
186
187 return $nullval;
188 }
189 $class = static::class;
190 Stripe::getLogger()->error("Stripe Notice: Undefined property of {$class} instance: {$k}");
191
192 return $nullval;
193 }
194
195 /**
196 * Magic method for var_dump output. Only works with PHP >= 5.6.
197 *
198 * @return array
199 */
200 public function __debugInfo()
201 {
202 return $this->_values;
203 }
204
205 // ArrayAccess methods
206
207 /**
208 * @return void
209 */
210 #[\ReturnTypeWillChange]
211 public function offsetSet($k, $v)
212 {
213 $this->{$k} = $v;
214 }
215
216 /**
217 * @return bool
218 */
219 #[\ReturnTypeWillChange]
220 public function offsetExists($k)
221 {
222 return \array_key_exists($k, $this->_values);
223 }
224
225 /**
226 * @return void
227 */
228 #[\ReturnTypeWillChange]
229 public function offsetUnset($k)
230 {
231 unset($this->{$k});
232 }
233
234 /**
235 * @return mixed
236 */
237 #[\ReturnTypeWillChange]
238 public function offsetGet($k)
239 {
240 return \array_key_exists($k, $this->_values) ? $this->_values[$k] : null;
241 }
242
243 /**
244 * @return int
245 */
246 #[\ReturnTypeWillChange]
247 public function count()
248 {
249 return \count($this->_values);
250 }
251
252 public function keys()
253 {
254 return \array_keys($this->_values);
255 }
256
257 public function values()
258 {
259 return \array_values($this->_values);
260 }
261
262 /**
263 * This unfortunately needs to be public to be used in Util\Util.
264 *
265 * @param array $values
266 * @param null|array|string|Util\RequestOptions $opts
267 *
268 * @return static the object constructed from the given values
269 */
270 public static function constructFrom($values, $opts = null)
271 {
272 $obj = new static(isset($values['id']) ? $values['id'] : null);
273 $obj->refreshFrom($values, $opts);
274
275 return $obj;
276 }
277
278 /**
279 * Refreshes this object using the provided values.
280 *
281 * @param array $values
282 * @param null|array|string|Util\RequestOptions $opts
283 * @param bool $partial defaults to false
284 */
285 public function refreshFrom($values, $opts, $partial = false)
286 {
287 $this->_opts = Util\RequestOptions::parse($opts);
288
289 $this->_originalValues = self::deepCopy($values);
290
291 if ($values instanceof StripeObject) {
292 $values = $values->toArray();
293 }
294
295 // Wipe old state before setting new. This is useful for e.g. updating a
296 // customer, where there is no persistent card parameter. Mark those values
297 // which don't persist as transient
298 if ($partial) {
299 $removed = new Util\Set();
300 } else {
301 $removed = new Util\Set(\array_diff(\array_keys($this->_values), \array_keys($values)));
302 }
303
304 foreach ($removed->toArray() as $k) {
305 unset($this->{$k});
306 }
307
308 $this->updateAttributes($values, $opts, false);
309 foreach ($values as $k => $v) {
310 $this->_transientValues->discard($k);
311 $this->_unsavedValues->discard($k);
312 }
313 }
314
315 /**
316 * Mass assigns attributes on the model.
317 *
318 * @param array $values
319 * @param null|array|string|Util\RequestOptions $opts
320 * @param bool $dirty defaults to true
321 */
322 public function updateAttributes($values, $opts = null, $dirty = true)
323 {
324 foreach ($values as $k => $v) {
325 // Special-case metadata to always be cast as a StripeObject
326 // This is necessary in case metadata is empty, as PHP arrays do
327 // not differentiate between lists and hashes, and we consider
328 // empty arrays to be lists.
329 if (('metadata' === $k) && (\is_array($v))) {
330 $this->_values[$k] = StripeObject::constructFrom($v, $opts);
331 } else {
332 $this->_values[$k] = Util\Util::convertToStripeObject($v, $opts);
333 }
334 if ($dirty) {
335 $this->dirtyValue($this->_values[$k]);
336 }
337 $this->_unsavedValues->add($k);
338 }
339 }
340
341 /**
342 * @param bool $force defaults to false
343 *
344 * @return array a recursive mapping of attributes to values for this object,
345 * including the proper value for deleted attributes
346 */
347 public function serializeParameters($force = false)
348 {
349 $updateParams = [];
350
351 foreach ($this->_values as $k => $v) {
352 // There are a few reasons that we may want to add in a parameter for
353 // update:
354 //
355 // 1. The `$force` option has been set.
356 // 2. We know that it was modified.
357 // 3. Its value is a StripeObject. A StripeObject may contain modified
358 // values within in that its parent StripeObject doesn't know about.
359 //
360 $original = \array_key_exists($k, $this->_originalValues) ? $this->_originalValues[$k] : null;
361 $unsaved = $this->_unsavedValues->includes($k);
362 if ($force || $unsaved || $v instanceof StripeObject) {
363 $updateParams[$k] = $this->serializeParamsValue(
364 $this->_values[$k],
365 $original,
366 $unsaved,
367 $force,
368 $k
369 );
370 }
371 }
372
373 // a `null` that makes it out of `serializeParamsValue` signals an empty
374 // value that we shouldn't appear in the serialized form of the object
375 return \array_filter(
376 $updateParams,
377 function ($v) {
378 return null !== $v;
379 }
380 );
381 }
382
383 public function serializeParamsValue($value, $original, $unsaved, $force, $key = null)
384 {
385 // The logic here is that essentially any object embedded in another
386 // object that had a `type` is actually an API resource of a different
387 // type that's been included in the response. These other resources must
388 // be updated from their proper endpoints, and therefore they are not
389 // included when serializing even if they've been modified.
390 //
391 // There are _some_ known exceptions though.
392 //
393 // For example, if the value is unsaved (meaning the user has set it), and
394 // it looks like the API resource is persisted with an ID, then we include
395 // the object so that parameters are serialized with a reference to its
396 // ID.
397 //
398 // Another example is that on save API calls it's sometimes desirable to
399 // update a customer's default source by setting a new card (or other)
400 // object with `->source=` and then saving the customer. The
401 // `saveWithParent` flag to override the default behavior allows us to
402 // handle these exceptions.
403 //
404 // We throw an error if a property was set explicitly but we can't do
405 // anything with it because the integration is probably not working as the
406 // user intended it to.
407 if (null === $value) {
408 return '';
409 }
410 if (($value instanceof ApiResource) && (!$value->saveWithParent)) {
411 if (!$unsaved) {
412 return null;
413 }
414 if (isset($value->id)) {
415 return $value;
416 }
417
418 throw new Exception\InvalidArgumentException(
419 "Cannot save property `{$key}` containing an API resource of type " .
420 \get_class($value) . ". It doesn't appear to be persisted and is " .
421 'not marked as `saveWithParent`.'
422 );
423 }
424 if (\is_array($value)) {
425 if (Util\Util::isList($value)) {
426 // Sequential array, i.e. a list
427 $update = [];
428 foreach ($value as $v) {
429 $update[] = $this->serializeParamsValue($v, null, true, $force);
430 }
431 // This prevents an array that's unchanged from being resent.
432 if ($update !== $this->serializeParamsValue($original, null, true, $force, $key)) {
433 return $update;
434 }
435 } else {
436 // Associative array, i.e. a map
437 return Util\Util::convertToStripeObject($value, $this->_opts)->serializeParameters();
438 }
439 } elseif ($value instanceof StripeObject) {
440 $update = $value->serializeParameters($force);
441 if ($original && $unsaved && $key && static::getAdditiveParams()->includes($key)) {
442 $update = \array_merge(self::emptyValues($original), $update);
443 }
444
445 return $update;
446 } else {
447 return $value;
448 }
449 }
450
451 /**
452 * @return mixed
453 */
454 #[\ReturnTypeWillChange]
455 public function jsonSerialize()
456 {
457 return $this->toArray();
458 }
459
460 /**
461 * Returns an associative array with the key and values composing the
462 * Stripe object.
463 *
464 * @return array the associative array
465 */
466 public function toArray()
467 {
468 $maybeToArray = function ($value) {
469 if (null === $value) {
470 return null;
471 }
472
473 return \is_object($value) && \method_exists($value, 'toArray') ? $value->toArray() : $value;
474 };
475
476 return \array_reduce(\array_keys($this->_values), function ($acc, $k) use ($maybeToArray) {
477 if ('_' === \substr((string) $k, 0, 1)) {
478 return $acc;
479 }
480 $v = $this->_values[$k];
481 if (Util\Util::isList($v)) {
482 $acc[$k] = \array_map($maybeToArray, $v);
483 } else {
484 $acc[$k] = $maybeToArray($v);
485 }
486
487 return $acc;
488 }, []);
489 }
490
491 /**
492 * Returns a pretty JSON representation of the Stripe object.
493 *
494 * @return string the JSON representation of the Stripe object
495 */
496 public function toJSON()
497 {
498 return \json_encode($this->toArray(), \JSON_PRETTY_PRINT);
499 }
500
501 public function __toString()
502 {
503 $class = static::class;
504
505 return $class . ' JSON: ' . $this->toJSON();
506 }
507
508 /**
509 * Sets all keys within the StripeObject as unsaved so that they will be
510 * included with an update when `serializeParameters` is called. This
511 * method is also recursive, so any StripeObjects contained as values or
512 * which are values in a tenant array are also marked as dirty.
513 */
514 public function dirty()
515 {
516 $this->_unsavedValues = new Util\Set(\array_keys($this->_values));
517 foreach ($this->_values as $k => $v) {
518 $this->dirtyValue($v);
519 }
520 }
521
522 protected function dirtyValue($value)
523 {
524 if (\is_array($value)) {
525 foreach ($value as $v) {
526 $this->dirtyValue($v);
527 }
528 } elseif ($value instanceof StripeObject) {
529 $value->dirty();
530 }
531 }
532
533 /**
534 * Produces a deep copy of the given object including support for arrays
535 * and StripeObjects.
536 *
537 * @param mixed $obj
538 */
539 protected static function deepCopy($obj)
540 {
541 if (\is_array($obj)) {
542 $copy = [];
543 foreach ($obj as $k => $v) {
544 $copy[$k] = self::deepCopy($v);
545 }
546
547 return $copy;
548 }
549 if ($obj instanceof StripeObject) {
550 return $obj::constructFrom(
551 self::deepCopy($obj->_values),
552 clone $obj->_opts
553 );
554 }
555
556 return $obj;
557 }
558
559 /**
560 * Returns a hash of empty values for all the values that are in the given
561 * StripeObject.
562 *
563 * @param mixed $obj
564 */
565 public static function emptyValues($obj)
566 {
567 if (\is_array($obj)) {
568 $values = $obj;
569 } elseif ($obj instanceof StripeObject) {
570 $values = $obj->_values;
571 } else {
572 throw new Exception\InvalidArgumentException(
573 'empty_values got unexpected object type: ' . \get_class($obj)
574 );
575 }
576
577 return \array_fill_keys(\array_keys($values), '');
578 }
579
580 /**
581 * @return null|ApiResponse The last response from the Stripe API
582 */
583 public function getLastResponse()
584 {
585 return $this->_lastResponse;
586 }
587
588 /**
589 * Sets the last response from the Stripe API.
590 *
591 * @param ApiResponse $resp
592 */
593 public function setLastResponse($resp)
594 {
595 $this->_lastResponse = $resp;
596 }
597
598 /**
599 * Indicates whether or not the resource has been deleted on the server.
600 * Note that some, but not all, resources can indicate whether they have
601 * been deleted.
602 *
603 * @return bool whether the resource is deleted
604 */
605 public function isDeleted()
606 {
607 return isset($this->_values['deleted']) ? $this->_values['deleted'] : false;
608 }
609 }
610