diff options
| author | manzerbredes <loic.guegan_secondary@yahoo.fr> | 2016-03-15 16:17:39 +0100 |
|---|---|---|
| committer | manzerbredes <loic.guegan_secondary@yahoo.fr> | 2016-03-15 16:17:39 +0100 |
| commit | 26d10bc0fa4befbac54453228ae1ce89021bdec2 (patch) | |
| tree | 029d7240ecf7416205e5f76cf9107a6b5bdf8ca3 /server/vendor/php-opencloud/common/src/Common/Resource | |
| parent | 8ad216dedf017f3d6de047a25d08db3b98e16361 (diff) | |
| parent | 03ef74d0cfe675a6e18a91f039182ca1b248d8f5 (diff) | |
Merge branch 'develop' into loic
Diffstat (limited to 'server/vendor/php-opencloud/common/src/Common/Resource')
10 files changed, 661 insertions, 0 deletions
diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php new file mode 100644 index 0000000..9f79b07 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php @@ -0,0 +1,243 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Api\Operator; +use OpenCloud\Common\Transport\Utils; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a top-level abstraction of a remote API resource. Usually a resource represents a discrete + * entity such as a Server, Container, Load Balancer. Apart from a representation of state, a resource can + * also execute RESTFul operations on itself (updating, deleting, listing) or on other models. + * + * @package OpenCloud\Common\Resource + */ +abstract class AbstractResource extends Operator implements ResourceInterface +{ + const DEFAULT_MARKER_KEY = 'id'; + + /** + * The JSON key that indicates how the API nests singular resources. For example, when + * performing a GET, it could respond with ``{"server": {"id": "12345"}}``. In this case, + * "server" is the resource key, since the essential state of the server is nested inside. + * + * @var string + */ + protected $resourceKey; + + /** + * The key that indicates how the API nests resource collections. For example, when + * performing a GET, it could respond with ``{"servers": [{}, {}]}``. In this case, "servers" + * is the resources key, since the array of servers is nested inside. + * + * @var string + */ + protected $resourcesKey; + + /** + * Indicates which attribute of the current resource should be used for pagination markers. + * + * @var string + */ + protected $markerKey; + + /** + * An array of aliases that will be checked when the resource is being populated. For example, + * + * 'FOO_BAR' => 'fooBar' + * + * will extract FOO_BAR from the response, and save it as 'fooBar' in the resource. + * + * @var array + */ + protected $aliases = []; + + /** + * Populates the current resource from a response object. + * + * @param ResponseInterface $response + * + * @return $this|ResourceInterface + */ + public function populateFromResponse(ResponseInterface $response) + { + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { + $json = Utils::jsonDecode($response); + if (!empty($json)) { + $this->populateFromArray(Utils::flattenJson($json, $this->resourceKey)); + } + } + + return $this; + } + + /** + * Populates the current resource from a data array. + * + * @param array $array + * + * @return mixed|void + */ + public function populateFromArray(array $array) + { + $reflClass = new \ReflectionClass($this); + + foreach ($array as $key => $val) { + $propertyName = isset($this->aliases[$key]) ? $this->aliases[$key] : $key; + + if (property_exists($this, $propertyName)) { + if ($type = $this->extractTypeFromDocBlock($reflClass, $propertyName)) { + $val = $this->parseDocBlockValue($type, $val); + } + + $this->$propertyName = $val; + } + } + } + + private function parseDocBlockValue($type, $val) + { + if (strpos($type, '[]') === 0 && is_array($val)) { + $array = []; + foreach ($val as $subVal) { + $array[] = $this->model($this->normalizeModelClass(substr($type, 2)), $subVal); + } + $val = $array; + } elseif (strcasecmp($type, '\datetimeimmutable') === 0) { + $val = new \DateTimeImmutable($val); + } elseif ($this->isNotNativeType($type)) { + $val = $this->model($this->normalizeModelClass($type), $val); + } + + return $val; + } + + private function isNotNativeType($type) + { + return !in_array($type, [ + 'string', 'bool', 'boolean', 'double', 'null', 'array', 'object', 'int', 'integer', 'float', 'numeric', + 'mixed' + ]); + } + + private function normalizeModelClass($class) + { + if (strpos($class, '\\') === false) { + $currentNamespace = (new \ReflectionClass($this))->getNamespaceName(); + $class = sprintf("%s\\%s", $currentNamespace, $class); + } + + return $class; + } + + private function extractTypeFromDocBlock(\ReflectionClass $reflClass, $propertyName) + { + $docComment = $reflClass->getProperty($propertyName)->getDocComment(); + + if (!$docComment) { + return false; + } + + $matches = []; + preg_match('#@var ((\[\])?[\w|\\\]+)#', $docComment, $matches); + return isset($matches[1]) ? $matches[1] : null; + } + + /** + * Internal method which retrieves the values of provided keys. + * + * @param array $keys + * + * @return array + */ + protected function getAttrs(array $keys) + { + $output = []; + + foreach ($keys as $key) { + if (property_exists($this, $key) && $this->$key !== null) { + $output[$key] = $this->$key; + } + } + + return $output; + } + + /** + * @param array $definition + * + * @return mixed + */ + public function executeWithState(array $definition) + { + return $this->execute($definition, $this->getAttrs(array_keys($definition['params']))); + } + + private function getResourcesKey() + { + $resourcesKey = $this->resourcesKey; + + if (!$resourcesKey) { + $class = substr(static::class, strrpos(static::class, '\\') + 1); + $resourcesKey = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $class)) . 's'; + } + + return $resourcesKey; + } + + /** + * {@inheritDoc} + */ + public function enumerate(array $def, array $userVals = [], callable $mapFn = null) + { + $operation = $this->getOperation($def); + + $requestFn = function ($marker) use ($operation, $userVals) { + if ($marker) { + $userVals['marker'] = $marker; + } + return $this->sendRequest($operation, $userVals); + }; + + $resourceFn = function (array $data) { + $resource = $this->newInstance(); + $resource->populateFromArray($data); + return $resource; + }; + + $opts = [ + 'limit' => isset($userVals['limit']) ? $userVals['limit'] : null, + 'resourcesKey' => $this->getResourcesKey(), + 'markerKey' => $this->markerKey, + 'mapFn' => $mapFn, + ]; + + $iterator = new Iterator($opts, $requestFn, $resourceFn); + return $iterator(); + } + + public function extractMultipleInstances(ResponseInterface $response, $key = null) + { + $key = $key ?: $this->getResourcesKey(); + $resourcesData = Utils::jsonDecode($response)[$key]; + + $resources = []; + + foreach ($resourcesData as $resourceData) { + $resource = $this->newInstance(); + $resource->populateFromArray($resourceData); + $resources[] = $resource; + } + + return $resources; + } + + protected function getService() + { + $class = static::class; + $service = substr($class, 0, strpos($class, 'Models') - 1) . '\\Service'; + + return new $service($this->client, $this->api); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php new file mode 100644 index 0000000..19579c1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php @@ -0,0 +1,19 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be created. + * + * @package OpenCloud\Common\Resource + */ +interface Creatable +{ + /** + * Create a new resource according to the configuration set in the options. + * + * @param array $userOptions + * @return self + */ + public function create(array $userOptions); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php new file mode 100644 index 0000000..eeb0602 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be deleted. + * + * @package OpenCloud\Common\Resource + */ +interface Deletable +{ + /** + * Permanently delete this resource. + * + * @return void + */ + public function delete(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php b/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php new file mode 100644 index 0000000..53b51fc --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php @@ -0,0 +1,67 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use Psr\Http\Message\ResponseInterface; + +interface HasMetadata +{ + /** + * Retrieves the metadata for the resource in the form of an associative array or hash. Each key represents the + * metadata item's name, and each value represents the metadata item's remote value. + * + * @return array + */ + public function getMetadata(); + + /** + * Merges a set of new values with those which already exist (on the remote API) for a resource. For example, if + * the resource has this metadata already set: + * + * Foo: val1 + * Bar: val2 + * + * and mergeMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Bar: val2 + * Baz: val4 + * + * You will notice that any metadata items which are not specified in the call are preserved. + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function mergeMetadata(array $metadata); + + /** + * Replaces all of the existing metadata items for a resource with a new set of values. Any metadata items which + * are not provided in the call are removed from the resource. For example, if the resource has this metadata + * already set: + * + * Foo: val1 + * Bar: val2 + * + * and resetMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Baz: val4 + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function resetMetadata(array $metadata); + + /** + * Extracts metadata from a response object and returns it in the form of an associative array. + * + * @param ResponseInterface $response + * + * @return array + */ + public function parseMetadata(ResponseInterface $response); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php new file mode 100644 index 0000000..519be7e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php @@ -0,0 +1,124 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Error\BadResponseError; + +/** + * Contains reusable functionality for resources that have long operations which require waiting in + * order to reach a particular state. + * + * @codeCoverageIgnore + * + * @package OpenCloud\Common\Resource + */ +trait HasWaiterTrait +{ + /** + * Provides a blocking operation until the resource has reached a particular state. The method + * will enter a loop, requesting feedback from the remote API until it sends back an appropriate + * status. + * + * @param string $status The state to be reached + * @param int $timeout The maximum timeout. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. + * @param int $sleepPeriod The amount of time to pause between each HTTP request. + */ + public function waitUntil($status, $timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + $this->retrieve(); + + if ($this->status == $status || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Provides a blocking operation until the resource has reached a particular state. The method + * will enter a loop, executing the callback until TRUE is returned. This provides great + * flexibility. + * + * @param callable $fn An anonymous function that will be executed on every iteration. You can + * encapsulate your own logic to determine whether the resource has + * successfully transitioned. When TRUE is returned by the callback, + * the loop will end. + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + * @param int $sleepPeriod The amount of time to pause between each HTTP request. + */ + public function waitWithCallback(callable $fn, $timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + $this->retrieve(); + + $response = call_user_func_array($fn, [$this]); + + if ($response === true || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Internal method used to identify whether a timeout has been exceeded. + * + * @param bool|int $timeout + * @param int $startTime + * + * @return bool + */ + private function shouldHalt($timeout, $startTime) + { + if ($timeout === false) { + return false; + } + + return time() - $startTime >= $timeout; + } + + /** + * Convenience method providing a blocking operation until the resource transitions to an + * ``ACTIVE`` status. + * + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + */ + public function waitUntilActive($timeout = false) + { + $this->waitUntil('ACTIVE', $timeout); + } + + public function waitUntilDeleted($timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + try { + $this->retrieve(); + } catch (BadResponseError $e) { + if ($e->getResponse()->getStatusCode() === 404) { + break; + } + throw $e; + } + + if ($this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php new file mode 100644 index 0000000..63d4455 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php @@ -0,0 +1,97 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Transport\Utils; + +class Iterator +{ + private $requestFn; + private $resourceFn; + private $limit; + private $count; + private $resourcesKey; + private $markerKey; + private $mapFn; + private $currentMarker; + + public function __construct(array $options, callable $requestFn, callable $resourceFn) + { + $this->limit = isset($options['limit']) ? $options['limit'] : false; + $this->count = 0; + + if (isset($options['resourcesKey'])) { + $this->resourcesKey = $options['resourcesKey']; + } + + if (isset($options['markerKey'])) { + $this->markerKey = $options['markerKey']; + } + + if (isset($options['mapFn']) && is_callable($options['mapFn'])) { + $this->mapFn = $options['mapFn']; + } + + $this->requestFn = $requestFn; + $this->resourceFn = $resourceFn; + } + + private function fetchResources() + { + if ($this->shouldNotSendAnotherRequest()) { + return false; + } + + $response = call_user_func($this->requestFn, $this->currentMarker); + + $json = Utils::flattenJson(Utils::jsonDecode($response), $this->resourcesKey); + + if ($response->getStatusCode() === 204 || empty($json)) { + return false; + } + + return $json; + } + + private function assembleResource(array $data) + { + $resource = call_user_func($this->resourceFn, $data); + + // Invoke user-provided fn if provided + if ($this->mapFn) { + call_user_func_array($this->mapFn, [&$resource]); + } + + // Update marker if operation supports it + if ($this->markerKey) { + $this->currentMarker = $resource->{$this->markerKey}; + } + + return $resource; + } + + private function totalReached() + { + return $this->limit && $this->count >= $this->limit; + } + + private function shouldNotSendAnotherRequest() + { + return $this->totalReached() || ($this->count > 0 && !$this->markerKey); + } + + public function __invoke() + { + while ($resources = $this->fetchResources()) { + foreach ($resources as $resourceData) { + if ($this->totalReached()) { + break; + } + + $this->count++; + + yield $this->assembleResource($resourceData); + } + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php new file mode 100644 index 0000000..8e255c0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php @@ -0,0 +1,28 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be enumerated (listed over). + * + * @package OpenCloud\Common\Resource + */ +interface Listable +{ + /** + * This method iterates over a collection of resources. It sends the operation's request to the API, + * parses the response, converts each element into {@see self} and - if pagination is supported - continues + * to send requests until an empty collection is received back. + * + * For paginated collections, it sends subsequent requests according to a marker URL query. The value + * of the marker will depend on the last element returned in the previous response. If a limit is + * provided, the loop will continue up until that point. + * + * @param array $def The operation definition + * @param array $userVals The user values + * @param callable $mapFn An optional callback that will be executed on every resource iteration. + * + * @returns void + */ + public function enumerate(array $def, array $userVals = [], callable $mapFn = null); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php b/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php new file mode 100644 index 0000000..8cf841b --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php @@ -0,0 +1,29 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use Psr\Http\Message\ResponseInterface; + +/** + * Represents an API resource. + * + * @package OpenCloud\Common\Resource + */ +interface ResourceInterface +{ + /** + * All models which represent an API resource should be able to be populated + * from a {@see ResponseInterface} object. + * + * @param ResponseInterface $response + * + * @return self + */ + public function populateFromResponse(ResponseInterface $response); + + /** + * @param array $data + * @return mixed + */ + public function populateFromArray(array $data); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php new file mode 100644 index 0000000..333b2c2 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * A resource that supports a GET or HEAD operation to retrieve more details. + * + * @package OpenCloud\Common\Resource + */ +interface Retrievable +{ + /** + * Retrieve details of the current resource from the remote API. + * + * @return void + */ + public function retrieve(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php new file mode 100644 index 0000000..a9a3f0e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be updated. + * + * @package OpenCloud\Common\Resource + */ +interface Updateable +{ + /** + * Update the current resource with the configuration set out in the user options. + * + * @return void + */ + public function update(); +} |
