vendor/stripe/stripe-php/lib/StripeObject.php line 10

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