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

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