summaryrefslogtreecommitdiffstats
path: root/vendor/stripe/stripe-php/lib/StripeObject.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/stripe/stripe-php/lib/StripeObject.php')
-rw-r--r--vendor/stripe/stripe-php/lib/StripeObject.php576
1 files changed, 576 insertions, 0 deletions
diff --git a/vendor/stripe/stripe-php/lib/StripeObject.php b/vendor/stripe/stripe-php/lib/StripeObject.php
new file mode 100644
index 0000000..eca01a0
--- /dev/null
+++ b/vendor/stripe/stripe-php/lib/StripeObject.php
@@ -0,0 +1,576 @@
+<?php
+
+namespace Stripe;
+
+/**
+ * Class StripeObject.
+ */
+class StripeObject implements \ArrayAccess, \Countable, \JsonSerializable
+{
+ /** @var Util\RequestOptions */
+ protected $_opts;
+
+ /** @var array */
+ protected $_originalValues;
+
+ /** @var array */
+ protected $_values;
+
+ /** @var Util\Set */
+ protected $_unsavedValues;
+
+ /** @var Util\Set */
+ protected $_transientValues;
+
+ /** @var null|array */
+ protected $_retrieveOptions;
+
+ /** @var null|ApiResponse */
+ protected $_lastResponse;
+
+ /**
+ * @return Util\Set Attributes that should not be sent to the API because
+ * they're not updatable (e.g. ID).
+ */
+ public static function getPermanentAttributes()
+ {
+ static $permanentAttributes = null;
+ if (null === $permanentAttributes) {
+ $permanentAttributes = new Util\Set([
+ 'id',
+ ]);
+ }
+
+ return $permanentAttributes;
+ }
+
+ /**
+ * Additive objects are subobjects in the API that don't have the same
+ * semantics as most subobjects, which are fully replaced when they're set.
+ *
+ * This is best illustrated by example. The `source` parameter sent when
+ * updating a subscription is *not* additive; if we set it:
+ *
+ * source[object]=card&source[number]=123
+ *
+ * We expect the old `source` object to have been overwritten completely. If
+ * the previous source had an `address_state` key associated with it and we
+ * didn't send one this time, that value of `address_state` is gone.
+ *
+ * By contrast, additive objects are those that will have new data added to
+ * them while keeping any existing data in place. The only known case of its
+ * use is for `metadata`, but it could in theory be more general. As an
+ * example, say we have a `metadata` object that looks like this on the
+ * server side:
+ *
+ * metadata = ["old" => "old_value"]
+ *
+ * If we update the object with `metadata[new]=new_value`, the server side
+ * object now has *both* fields:
+ *
+ * metadata = ["old" => "old_value", "new" => "new_value"]
+ *
+ * This is okay in itself because usually users will want to treat it as
+ * additive:
+ *
+ * $obj->metadata["new"] = "new_value";
+ * $obj->save();
+ *
+ * However, in other cases, they may want to replace the entire existing
+ * contents:
+ *
+ * $obj->metadata = ["new" => "new_value"];
+ * $obj->save();
+ *
+ * This is where things get a little bit tricky because in order to clear
+ * any old keys that may have existed, we actually have to send an explicit
+ * empty string to the server. So the operation above would have to send
+ * this form to get the intended behavior:
+ *
+ * metadata[old]=&metadata[new]=new_value
+ *
+ * This method allows us to track which parameters are considered additive,
+ * and lets us behave correctly where appropriate when serializing
+ * parameters to be sent.
+ *
+ * @return Util\Set Set of additive parameters
+ */
+ public static function getAdditiveParams()
+ {
+ static $additiveParams = null;
+ if (null === $additiveParams) {
+ // Set `metadata` as additive so that when it's set directly we remember
+ // to clear keys that may have been previously set by sending empty
+ // values for them.
+ //
+ // It's possible that not every object has `metadata`, but having this
+ // option set when there is no `metadata` field is not harmful.
+ $additiveParams = new Util\Set([
+ 'metadata',
+ ]);
+ }
+
+ return $additiveParams;
+ }
+
+ public function __construct($id = null, $opts = null)
+ {
+ list($id, $this->_retrieveOptions) = Util\Util::normalizeId($id);
+ $this->_opts = Util\RequestOptions::parse($opts);
+ $this->_originalValues = [];
+ $this->_values = [];
+ $this->_unsavedValues = new Util\Set();
+ $this->_transientValues = new Util\Set();
+ if (null !== $id) {
+ $this->_values['id'] = $id;
+ }
+ }
+
+ // Standard accessor magic methods
+ public function __set($k, $v)
+ {
+ if (static::getPermanentAttributes()->includes($k)) {
+ throw new Exception\InvalidArgumentException(
+ "Cannot set {$k} on this object. HINT: you can't set: " .
+ \implode(', ', static::getPermanentAttributes()->toArray())
+ );
+ }
+
+ if ('' === $v) {
+ throw new Exception\InvalidArgumentException(
+ 'You cannot set \'' . $k . '\'to an empty string. '
+ . 'We interpret empty strings as NULL in requests. '
+ . 'You may set obj->' . $k . ' = NULL to delete the property'
+ );
+ }
+
+ $this->_values[$k] = Util\Util::convertToStripeObject($v, $this->_opts);
+ $this->dirtyValue($this->_values[$k]);
+ $this->_unsavedValues->add($k);
+ }
+
+ public function __isset($k)
+ {
+ return isset($this->_values[$k]);
+ }
+
+ public function __unset($k)
+ {
+ unset($this->_values[$k]);
+ $this->_transientValues->add($k);
+ $this->_unsavedValues->discard($k);
+ }
+
+ public function &__get($k)
+ {
+ // function should return a reference, using $nullval to return a reference to null
+ $nullval = null;
+ if (!empty($this->_values) && \array_key_exists($k, $this->_values)) {
+ return $this->_values[$k];
+ }
+ if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) {
+ $class = static::class;
+ $attrs = \implode(', ', \array_keys($this->_values));
+ $message = "Stripe Notice: Undefined property of {$class} instance: {$k}. "
+ . "HINT: The {$k} attribute was set in the past, however. "
+ . 'It was then wiped when refreshing the object '
+ . "with the result returned by Stripe's API, "
+ . 'probably as a result of a save(). The attributes currently '
+ . "available on this object are: {$attrs}";
+ Stripe::getLogger()->error($message);
+
+ return $nullval;
+ }
+ $class = static::class;
+ Stripe::getLogger()->error("Stripe Notice: Undefined property of {$class} instance: {$k}");
+
+ return $nullval;
+ }
+
+ // Magic method for var_dump output. Only works with PHP >= 5.6
+ public function __debugInfo()
+ {
+ return $this->_values;
+ }
+
+ // ArrayAccess methods
+ public function offsetSet($k, $v)
+ {
+ $this->{$k} = $v;
+ }
+
+ public function offsetExists($k)
+ {
+ return \array_key_exists($k, $this->_values);
+ }
+
+ public function offsetUnset($k)
+ {
+ unset($this->{$k});
+ }
+
+ public function offsetGet($k)
+ {
+ return \array_key_exists($k, $this->_values) ? $this->_values[$k] : null;
+ }
+
+ // Countable method
+ public function count()
+ {
+ return \count($this->_values);
+ }
+
+ public function keys()
+ {
+ return \array_keys($this->_values);
+ }
+
+ public function values()
+ {
+ return \array_values($this->_values);
+ }
+
+ /**
+ * This unfortunately needs to be public to be used in Util\Util.
+ *
+ * @param array $values
+ * @param null|array|string|Util\RequestOptions $opts
+ *
+ * @return static the object constructed from the given values
+ */
+ public static function constructFrom($values, $opts = null)
+ {
+ $obj = new static(isset($values['id']) ? $values['id'] : null);
+ $obj->refreshFrom($values, $opts);
+
+ return $obj;
+ }
+
+ /**
+ * Refreshes this object using the provided values.
+ *
+ * @param array $values
+ * @param null|array|string|Util\RequestOptions $opts
+ * @param bool $partial defaults to false
+ */
+ public function refreshFrom($values, $opts, $partial = false)
+ {
+ $this->_opts = Util\RequestOptions::parse($opts);
+
+ $this->_originalValues = self::deepCopy($values);
+
+ if ($values instanceof StripeObject) {
+ $values = $values->toArray();
+ }
+
+ // Wipe old state before setting new. This is useful for e.g. updating a
+ // customer, where there is no persistent card parameter. Mark those values
+ // which don't persist as transient
+ if ($partial) {
+ $removed = new Util\Set();
+ } else {
+ $removed = new Util\Set(\array_diff(\array_keys($this->_values), \array_keys($values)));
+ }
+
+ foreach ($removed->toArray() as $k) {
+ unset($this->{$k});
+ }
+
+ $this->updateAttributes($values, $opts, false);
+ foreach ($values as $k => $v) {
+ $this->_transientValues->discard($k);
+ $this->_unsavedValues->discard($k);
+ }
+ }
+
+ /**
+ * Mass assigns attributes on the model.
+ *
+ * @param array $values
+ * @param null|array|string|Util\RequestOptions $opts
+ * @param bool $dirty defaults to true
+ */
+ public function updateAttributes($values, $opts = null, $dirty = true)
+ {
+ foreach ($values as $k => $v) {
+ // Special-case metadata to always be cast as a StripeObject
+ // This is necessary in case metadata is empty, as PHP arrays do
+ // not differentiate between lists and hashes, and we consider
+ // empty arrays to be lists.
+ if (('metadata' === $k) && (\is_array($v))) {
+ $this->_values[$k] = StripeObject::constructFrom($v, $opts);
+ } else {
+ $this->_values[$k] = Util\Util::convertToStripeObject($v, $opts);
+ }
+ if ($dirty) {
+ $this->dirtyValue($this->_values[$k]);
+ }
+ $this->_unsavedValues->add($k);
+ }
+ }
+
+ /**
+ * @param bool $force defaults to false
+ *
+ * @return array a recursive mapping of attributes to values for this object,
+ * including the proper value for deleted attributes
+ */
+ public function serializeParameters($force = false)
+ {
+ $updateParams = [];
+
+ foreach ($this->_values as $k => $v) {
+ // There are a few reasons that we may want to add in a parameter for
+ // update:
+ //
+ // 1. The `$force` option has been set.
+ // 2. We know that it was modified.
+ // 3. Its value is a StripeObject. A StripeObject may contain modified
+ // values within in that its parent StripeObject doesn't know about.
+ //
+ $original = \array_key_exists($k, $this->_originalValues) ? $this->_originalValues[$k] : null;
+ $unsaved = $this->_unsavedValues->includes($k);
+ if ($force || $unsaved || $v instanceof StripeObject) {
+ $updateParams[$k] = $this->serializeParamsValue(
+ $this->_values[$k],
+ $original,
+ $unsaved,
+ $force,
+ $k
+ );
+ }
+ }
+
+ // a `null` that makes it out of `serializeParamsValue` signals an empty
+ // value that we shouldn't appear in the serialized form of the object
+ return \array_filter(
+ $updateParams,
+ function ($v) {
+ return null !== $v;
+ }
+ );
+ }
+
+ public function serializeParamsValue($value, $original, $unsaved, $force, $key = null)
+ {
+ // The logic here is that essentially any object embedded in another
+ // object that had a `type` is actually an API resource of a different
+ // type that's been included in the response. These other resources must
+ // be updated from their proper endpoints, and therefore they are not
+ // included when serializing even if they've been modified.
+ //
+ // There are _some_ known exceptions though.
+ //
+ // For example, if the value is unsaved (meaning the user has set it), and
+ // it looks like the API resource is persisted with an ID, then we include
+ // the object so that parameters are serialized with a reference to its
+ // ID.
+ //
+ // Another example is that on save API calls it's sometimes desirable to
+ // update a customer's default source by setting a new card (or other)
+ // object with `->source=` and then saving the customer. The
+ // `saveWithParent` flag to override the default behavior allows us to
+ // handle these exceptions.
+ //
+ // We throw an error if a property was set explicitly but we can't do
+ // anything with it because the integration is probably not working as the
+ // user intended it to.
+ if (null === $value) {
+ return '';
+ }
+ if (($value instanceof ApiResource) && (!$value->saveWithParent)) {
+ if (!$unsaved) {
+ return null;
+ }
+ if (isset($value->id)) {
+ return $value;
+ }
+
+ throw new Exception\InvalidArgumentException(
+ "Cannot save property `{$key}` containing an API resource of type " .
+ \get_class($value) . ". It doesn't appear to be persisted and is " .
+ 'not marked as `saveWithParent`.'
+ );
+ }
+ if (\is_array($value)) {
+ if (Util\Util::isList($value)) {
+ // Sequential array, i.e. a list
+ $update = [];
+ foreach ($value as $v) {
+ $update[] = $this->serializeParamsValue($v, null, true, $force);
+ }
+ // This prevents an array that's unchanged from being resent.
+ if ($update !== $this->serializeParamsValue($original, null, true, $force, $key)) {
+ return $update;
+ }
+ } else {
+ // Associative array, i.e. a map
+ return Util\Util::convertToStripeObject($value, $this->_opts)->serializeParameters();
+ }
+ } elseif ($value instanceof StripeObject) {
+ $update = $value->serializeParameters($force);
+ if ($original && $unsaved && $key && static::getAdditiveParams()->includes($key)) {
+ $update = \array_merge(self::emptyValues($original), $update);
+ }
+
+ return $update;
+ } else {
+ return $value;
+ }
+ }
+
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * Returns an associative array with the key and values composing the
+ * Stripe object.
+ *
+ * @return array the associative array
+ */
+ public function toArray()
+ {
+ $maybeToArray = function ($value) {
+ if (null === $value) {
+ return null;
+ }
+
+ return \is_object($value) && \method_exists($value, 'toArray') ? $value->toArray() : $value;
+ };
+
+ return \array_reduce(\array_keys($this->_values), function ($acc, $k) use ($maybeToArray) {
+ if ('_' === \substr((string) $k, 0, 1)) {
+ return $acc;
+ }
+ $v = $this->_values[$k];
+ if (Util\Util::isList($v)) {
+ $acc[$k] = \array_map($maybeToArray, $v);
+ } else {
+ $acc[$k] = $maybeToArray($v);
+ }
+
+ return $acc;
+ }, []);
+ }
+
+ /**
+ * Returns a pretty JSON representation of the Stripe object.
+ *
+ * @return string the JSON representation of the Stripe object
+ */
+ public function toJSON()
+ {
+ return \json_encode($this->toArray(), \JSON_PRETTY_PRINT);
+ }
+
+ public function __toString()
+ {
+ $class = static::class;
+
+ return $class . ' JSON: ' . $this->toJSON();
+ }
+
+ /**
+ * Sets all keys within the StripeObject as unsaved so that they will be
+ * included with an update when `serializeParameters` is called. This
+ * method is also recursive, so any StripeObjects contained as values or
+ * which are values in a tenant array are also marked as dirty.
+ */
+ public function dirty()
+ {
+ $this->_unsavedValues = new Util\Set(\array_keys($this->_values));
+ foreach ($this->_values as $k => $v) {
+ $this->dirtyValue($v);
+ }
+ }
+
+ protected function dirtyValue($value)
+ {
+ if (\is_array($value)) {
+ foreach ($value as $v) {
+ $this->dirtyValue($v);
+ }
+ } elseif ($value instanceof StripeObject) {
+ $value->dirty();
+ }
+ }
+
+ /**
+ * Produces a deep copy of the given object including support for arrays
+ * and StripeObjects.
+ *
+ * @param mixed $obj
+ */
+ protected static function deepCopy($obj)
+ {
+ if (\is_array($obj)) {
+ $copy = [];
+ foreach ($obj as $k => $v) {
+ $copy[$k] = self::deepCopy($v);
+ }
+
+ return $copy;
+ }
+ if ($obj instanceof StripeObject) {
+ return $obj::constructFrom(
+ self::deepCopy($obj->_values),
+ clone $obj->_opts
+ );
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Returns a hash of empty values for all the values that are in the given
+ * StripeObject.
+ *
+ * @param mixed $obj
+ */
+ public static function emptyValues($obj)
+ {
+ if (\is_array($obj)) {
+ $values = $obj;
+ } elseif ($obj instanceof StripeObject) {
+ $values = $obj->_values;
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'empty_values got unexpected object type: ' . \get_class($obj)
+ );
+ }
+
+ return \array_fill_keys(\array_keys($values), '');
+ }
+
+ /**
+ * @return null|ApiResponse The last response from the Stripe API
+ */
+ public function getLastResponse()
+ {
+ return $this->_lastResponse;
+ }
+
+ /**
+ * Sets the last response from the Stripe API.
+ *
+ * @param ApiResponse $resp
+ */
+ public function setLastResponse($resp)
+ {
+ $this->_lastResponse = $resp;
+ }
+
+ /**
+ * Indicates whether or not the resource has been deleted on the server.
+ * Note that some, but not all, resources can indicate whether they have
+ * been deleted.
+ *
+ * @return bool whether the resource is deleted
+ */
+ public function isDeleted()
+ {
+ return isset($this->_values['deleted']) ? $this->_values['deleted'] : false;
+ }
+}