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

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.      *
  239.      * @return static the object constructed from the given values
  240.      */
  241.     public static function constructFrom($values$opts null)
  242.     {
  243.         $obj = new static(isset($values['id']) ? $values['id'] : null);
  244.         $obj->refreshFrom($values$opts);
  245.         return $obj;
  246.     }
  247.     /**
  248.      * Refreshes this object using the provided values.
  249.      *
  250.      * @param array $values
  251.      * @param null|array|string|Util\RequestOptions $opts
  252.      * @param bool $partial defaults to false
  253.      */
  254.     public function refreshFrom($values$opts$partial false)
  255.     {
  256.         $this->_opts Util\RequestOptions::parse($opts);
  257.         $this->_originalValues self::deepCopy($values);
  258.         if ($values instanceof StripeObject) {
  259.             $values $values->toArray();
  260.         }
  261.         // Wipe old state before setting new.  This is useful for e.g. updating a
  262.         // customer, where there is no persistent card parameter.  Mark those values
  263.         // which don't persist as transient
  264.         if ($partial) {
  265.             $removed = new Util\Set();
  266.         } else {
  267.             $removed = new Util\Set(\array_diff(\array_keys($this->_values), \array_keys($values)));
  268.         }
  269.         foreach ($removed->toArray() as $k) {
  270.             unset($this->{$k});
  271.         }
  272.         $this->updateAttributes($values$optsfalse);
  273.         foreach ($values as $k => $v) {
  274.             $this->_transientValues->discard($k);
  275.             $this->_unsavedValues->discard($k);
  276.         }
  277.     }
  278.     /**
  279.      * Mass assigns attributes on the model.
  280.      *
  281.      * @param array $values
  282.      * @param null|array|string|Util\RequestOptions $opts
  283.      * @param bool $dirty defaults to true
  284.      */
  285.     public function updateAttributes($values$opts null$dirty true)
  286.     {
  287.         foreach ($values as $k => $v) {
  288.             // Special-case metadata to always be cast as a StripeObject
  289.             // This is necessary in case metadata is empty, as PHP arrays do
  290.             // not differentiate between lists and hashes, and we consider
  291.             // empty arrays to be lists.
  292.             if (('metadata' === $k) && (\is_array($v))) {
  293.                 $this->_values[$k] = StripeObject::constructFrom($v$opts);
  294.             } else {
  295.                 $this->_values[$k] = Util\Util::convertToStripeObject($v$opts);
  296.             }
  297.             if ($dirty) {
  298.                 $this->dirtyValue($this->_values[$k]);
  299.             }
  300.             $this->_unsavedValues->add($k);
  301.         }
  302.     }
  303.     /**
  304.      * @param bool $force defaults to false
  305.      *
  306.      * @return array a recursive mapping of attributes to values for this object,
  307.      *    including the proper value for deleted attributes
  308.      */
  309.     public function serializeParameters($force false)
  310.     {
  311.         $updateParams = [];
  312.         foreach ($this->_values as $k => $v) {
  313.             // There are a few reasons that we may want to add in a parameter for
  314.             // update:
  315.             //
  316.             //   1. The `$force` option has been set.
  317.             //   2. We know that it was modified.
  318.             //   3. Its value is a StripeObject. A StripeObject may contain modified
  319.             //      values within in that its parent StripeObject doesn't know about.
  320.             //
  321.             $original \array_key_exists($k$this->_originalValues) ? $this->_originalValues[$k] : null;
  322.             $unsaved $this->_unsavedValues->includes($k);
  323.             if ($force || $unsaved || $v instanceof StripeObject) {
  324.                 $updateParams[$k] = $this->serializeParamsValue(
  325.                     $this->_values[$k],
  326.                     $original,
  327.                     $unsaved,
  328.                     $force,
  329.                     $k
  330.                 );
  331.             }
  332.         }
  333.         // a `null` that makes it out of `serializeParamsValue` signals an empty
  334.         // value that we shouldn't appear in the serialized form of the object
  335.         return \array_filter(
  336.             $updateParams,
  337.             function ($v) {
  338.                 return null !== $v;
  339.             }
  340.         );
  341.     }
  342.     public function serializeParamsValue($value$original$unsaved$force$key null)
  343.     {
  344.         // The logic here is that essentially any object embedded in another
  345.         // object that had a `type` is actually an API resource of a different
  346.         // type that's been included in the response. These other resources must
  347.         // be updated from their proper endpoints, and therefore they are not
  348.         // included when serializing even if they've been modified.
  349.         //
  350.         // There are _some_ known exceptions though.
  351.         //
  352.         // For example, if the value is unsaved (meaning the user has set it), and
  353.         // it looks like the API resource is persisted with an ID, then we include
  354.         // the object so that parameters are serialized with a reference to its
  355.         // ID.
  356.         //
  357.         // Another example is that on save API calls it's sometimes desirable to
  358.         // update a customer's default source by setting a new card (or other)
  359.         // object with `->source=` and then saving the customer. The
  360.         // `saveWithParent` flag to override the default behavior allows us to
  361.         // handle these exceptions.
  362.         //
  363.         // We throw an error if a property was set explicitly but we can't do
  364.         // anything with it because the integration is probably not working as the
  365.         // user intended it to.
  366.         if (null === $value) {
  367.             return '';
  368.         }
  369.         if (($value instanceof ApiResource) && (!$value->saveWithParent)) {
  370.             if (!$unsaved) {
  371.                 return null;
  372.             }
  373.             if (isset($value->id)) {
  374.                 return $value;
  375.             }
  376.             throw new Exception\InvalidArgumentException(
  377.                 "Cannot save property `{$key}` containing an API resource of type " .
  378.                     \get_class($value) . ". It doesn't appear to be persisted and is " .
  379.                     'not marked as `saveWithParent`.'
  380.             );
  381.         }
  382.         if (\is_array($value)) {
  383.             if (Util\Util::isList($value)) {
  384.                 // Sequential array, i.e. a list
  385.                 $update = [];
  386.                 foreach ($value as $v) {
  387.                     $update[] = $this->serializeParamsValue($vnulltrue$force);
  388.                 }
  389.                 // This prevents an array that's unchanged from being resent.
  390.                 if ($update !== $this->serializeParamsValue($originalnulltrue$force$key)) {
  391.                     return $update;
  392.                 }
  393.             } else {
  394.                 // Associative array, i.e. a map
  395.                 return Util\Util::convertToStripeObject($value$this->_opts)->serializeParameters();
  396.             }
  397.         } elseif ($value instanceof StripeObject) {
  398.             $update $value->serializeParameters($force);
  399.             if ($original && $unsaved && $key && static::getAdditiveParams()->includes($key)) {
  400.                 $update \array_merge(self::emptyValues($original), $update);
  401.             }
  402.             return $update;
  403.         } else {
  404.             return $value;
  405.         }
  406.     }
  407.     /**
  408.      * @return mixed
  409.      */
  410.     #[\ReturnTypeWillChange]
  411.     public function jsonSerialize()
  412.     {
  413.         return $this->toArray();
  414.     }
  415.     /**
  416.      * Returns an associative array with the key and values composing the
  417.      * Stripe object.
  418.      *
  419.      * @return array the associative array
  420.      */
  421.     public function toArray()
  422.     {
  423.         $maybeToArray = function ($value) {
  424.             if (null === $value) {
  425.                 return null;
  426.             }
  427.             return \is_object($value) && \method_exists($value'toArray') ? $value->toArray() : $value;
  428.         };
  429.         return \array_reduce(\array_keys($this->_values), function ($acc$k) use ($maybeToArray) {
  430.             if ('_' === \substr((string) $k01)) {
  431.                 return $acc;
  432.             }
  433.             $v $this->_values[$k];
  434.             if (Util\Util::isList($v)) {
  435.                 $acc[$k] = \array_map($maybeToArray$v);
  436.             } else {
  437.                 $acc[$k] = $maybeToArray($v);
  438.             }
  439.             return $acc;
  440.         }, []);
  441.     }
  442.     /**
  443.      * Returns a pretty JSON representation of the Stripe object.
  444.      *
  445.      * @return string the JSON representation of the Stripe object
  446.      */
  447.     public function toJSON()
  448.     {
  449.         return \json_encode($this->toArray(), \JSON_PRETTY_PRINT);
  450.     }
  451.     public function __toString()
  452.     {
  453.         $class = static::class;
  454.         return $class ' JSON: ' $this->toJSON();
  455.     }
  456.     /**
  457.      * Sets all keys within the StripeObject as unsaved so that they will be
  458.      * included with an update when `serializeParameters` is called. This
  459.      * method is also recursive, so any StripeObjects contained as values or
  460.      * which are values in a tenant array are also marked as dirty.
  461.      */
  462.     public function dirty()
  463.     {
  464.         $this->_unsavedValues = new Util\Set(\array_keys($this->_values));
  465.         foreach ($this->_values as $k => $v) {
  466.             $this->dirtyValue($v);
  467.         }
  468.     }
  469.     protected function dirtyValue($value)
  470.     {
  471.         if (\is_array($value)) {
  472.             foreach ($value as $v) {
  473.                 $this->dirtyValue($v);
  474.             }
  475.         } elseif ($value instanceof StripeObject) {
  476.             $value->dirty();
  477.         }
  478.     }
  479.     /**
  480.      * Produces a deep copy of the given object including support for arrays
  481.      * and StripeObjects.
  482.      *
  483.      * @param mixed $obj
  484.      */
  485.     protected static function deepCopy($obj)
  486.     {
  487.         if (\is_array($obj)) {
  488.             $copy = [];
  489.             foreach ($obj as $k => $v) {
  490.                 $copy[$k] = self::deepCopy($v);
  491.             }
  492.             return $copy;
  493.         }
  494.         if ($obj instanceof StripeObject) {
  495.             return $obj::constructFrom(
  496.                 self::deepCopy($obj->_values),
  497.                 clone $obj->_opts
  498.             );
  499.         }
  500.         return $obj;
  501.     }
  502.     /**
  503.      * Returns a hash of empty values for all the values that are in the given
  504.      * StripeObject.
  505.      *
  506.      * @param mixed $obj
  507.      */
  508.     public static function emptyValues($obj)
  509.     {
  510.         if (\is_array($obj)) {
  511.             $values $obj;
  512.         } elseif ($obj instanceof StripeObject) {
  513.             $values $obj->_values;
  514.         } else {
  515.             throw new Exception\InvalidArgumentException(
  516.                 'empty_values got unexpected object type: ' \get_class($obj)
  517.             );
  518.         }
  519.         return \array_fill_keys(\array_keys($values), '');
  520.     }
  521.     /**
  522.      * @return null|ApiResponse The last response from the Stripe API
  523.      */
  524.     public function getLastResponse()
  525.     {
  526.         return $this->_lastResponse;
  527.     }
  528.     /**
  529.      * Sets the last response from the Stripe API.
  530.      *
  531.      * @param ApiResponse $resp
  532.      */
  533.     public function setLastResponse($resp)
  534.     {
  535.         $this->_lastResponse $resp;
  536.     }
  537.     /**
  538.      * Indicates whether or not the resource has been deleted on the server.
  539.      * Note that some, but not all, resources can indicate whether they have
  540.      * been deleted.
  541.      *
  542.      * @return bool whether the resource is deleted
  543.      */
  544.     public function isDeleted()
  545.     {
  546.         return isset($this->_values['deleted']) ? $this->_values['deleted'] : false;
  547.     }
  548. }