Viewing file: TelnyxObject.php (18.89 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Telnyx;
/** * Class TelnyxObject * * @package Telnyx */ class TelnyxObject implements \ArrayAccess, \Countable, \JsonSerializable { protected $_opts; protected $_originalValues; protected $_values; protected $_unsavedValues; protected $_transientValues; protected $_retrieveOptions; 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 ($permanentAttributes === null) { $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 ($additiveParams === null) { // 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 ($id !== null) { $this->_values['id'] = $id; } }
// Standard accessor magic methods public function __set($k, $v) { if (static::getPermanentAttributes()->includes($k)) { throw new \InvalidArgumentException( "Cannot set $k on this object. HINT: you can't set: " . join(', ', static::getPermanentAttributes()->toArray()) ); }
if ($v === "") { throw new \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::convertToTelnyxObject($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]; } elseif (!empty($this->_transientValues) && $this->_transientValues->includes($k)) { $class = get_class($this); $attrs = join(', ', array_keys($this->_values)); $message = "Telnyx 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 Telnyx's API, " . "probably as a result of a save(). The attributes currently " . "available on this object are: $attrs"; Telnyx::getLogger()->error($message); return $nullval; } else { $class = get_class($this); Telnyx::getLogger()->error("Telnyx 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|string|array|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);
// If 'id' is called something else like 'call_control_id' $class = get_class($obj); if (defined($class . '::OBJECT_ID')) { $obj->reassignId(static::OBJECT_ID); }
return $obj; }
/** * Refreshes this object using the provided values. * * @param array $values * @param null|string|array|Util\RequestOptions $opts * @param boolean $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 TelnyxObject) { $values = $values->toArray(true); }
// 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|string|array|Util\RequestOptions $opts * @param boolean $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 TelnyxObject // 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 (($k === "metadata") && (is_array($v))) { $this->_values[$k] = TelnyxObject::constructFrom($v, $opts); } else { $this->_values[$k] = Util\Util::convertToTelnyxObject($v, $opts); } if ($dirty) { $this->dirtyValue($this->_values[$k]); } $this->_unsavedValues->add($k); } }
/** * @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 TelnyxObject. A TelnyxObject may contain modified // values within in that its parent TelnyxObject 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 TelnyxObject) { $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 $updateParams = array_filter( $updateParams, function ($v) { return $v !== null; } );
return $updateParams; }
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 ($value === null) { return ""; } elseif (($value instanceof APIResource) && (!$value->saveWithParent)) { if (!$unsaved) { return null; } elseif (isset($value->id)) { return $value; } else { throw new \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`." ); } } elseif (is_array($value)) { if (Util\Util::isList($value)) { // Sequential array, i.e. a list $update = []; foreach ($value as $v) { array_push($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::convertToTelnyxObject($value, $this->_opts)->serializeParameters(); } } elseif ($value instanceof TelnyxObject) { $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(true); }
/** * 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 = \get_class($this);
return $class . ' JSON: ' . $this->toJSON(); }
/* public function __toArray($recursive = false) { if ($recursive) { return Util\Util::convertTelnyxObjectToArray($this->_values); } else { return $this->_values; } } */
/** * Sets all keys within the TelnyxObject as unsaved so that they will be * included with an update when `serializeParameters` is called. This * method is also recursive, so any TelnyxObjects 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 TelnyxObject) { $value->dirty(); } }
/** * Produces a deep copy of the given object including support for arrays * and TelnyxObjects. */ protected static function deepCopy($obj) { if (is_array($obj)) { $copy = []; foreach ($obj as $k => $v) { $copy[$k] = self::deepCopy($v); } return $copy; } elseif ($obj instanceof TelnyxObject) { return $obj::constructFrom( self::deepCopy($obj->_values), clone $obj->_opts ); } else { return $obj; } }
/** * Returns a hash of empty values for all the values that are in the given * TelnyxObject. */ public static function emptyValues($obj) { if (is_array($obj)) { $values = $obj; } elseif ($obj instanceof TelnyxObject) { $values = $obj->_values; } else { throw new \InvalidArgumentException( "empty_values got got unexpected object type: " . get_class($obj) ); } $update = array_fill_keys(array_keys($values), ""); return $update; }
/** * @return object The last response from the Telnyx API */ public function getLastResponse() { return $this->_lastResponse; }
/** * Sets the last response from the Telnyx API * * @param ApiResponse $resp * @return void */ 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; }
/** * Reassigns another parameter as the id. Used for endpoints that return id in a different param such as /v2/calls * @return void */ public function reassignId($new_id) { if (isset($this->_values[$new_id])) { $this->_values['id'] = $this->_values[$new_id]; } } }
|