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$optsfalse$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$optsfalse$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($vnulltrue$force);
  391.                 }
  392.                 // This prevents an array that's unchanged from being resent.
  393.                 if ($update !== $this->serializeParamsValue($originalnulltrue$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) $k01)) {
  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. }