Skip to content

Commit 37ed3c9

Browse files
author
Jamie Hannaford
committed
Add better integration tests
1 parent 87b777a commit 37ed3c9

12 files changed

+307
-183
lines changed

src/Common/Api/OperatorInterface.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,21 @@ public function execute(array $definition, array $userValues = []): ResponseInte
4444
public function executeAsync(array $definition, array $userValues = []): PromiseInterface;
4545

4646
/**
47-
* @param string $name The name of the model class.
47+
* Retrieves a populated Operation according to the definition and values provided. A
48+
* HTTP client is also injected into the object to allow it to communicate with the remote API.
49+
*
50+
* @param array $definition The data that dictates how the operation works
51+
*
52+
* @return Operation
53+
*/
54+
public function getOperation(array $definition): Operation;
55+
56+
/**
57+
* @param string $class The name of the model class.
4858
* @param mixed $data Either a {@see ResponseInterface} or data array that will populate the newly
4959
* created model class.
5060
*
5161
* @return \OpenCloud\Common\Resource\ResourceInterface
5262
*/
53-
public function model(string $name, $data = null): ResourceInterface;
63+
public function model(string $class, $data = null): ResourceInterface;
5464
}

src/Common/Api/Operator.php renamed to src/Common/Api/OperatorTrait.php

Lines changed: 45 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
use OpenCloud\Common\Transport\RequestSerializer;
1212
use Psr\Http\Message\ResponseInterface;
1313

14-
/**
15-
* {@inheritDoc}
16-
*/
17-
abstract class Operator implements OperatorInterface
14+
trait OperatorTrait
1815
{
1916
/** @var ClientInterface */
2017
protected $client;
@@ -55,18 +52,58 @@ public function __debugInfo()
5552
}
5653

5754
/**
58-
* Retrieves a populated Operation according to the definition and values provided. A
59-
* HTTP client is also injected into the object to allow it to communicate with the remote API.
55+
* Magic method which intercepts async calls, finds the sequential version, and wraps it in a
56+
* {@see Promise} object. In order for this to happen, the called methods need to be in the
57+
* following format: `createAsync`, where `create` is the sequential method being wrapped.
58+
*
59+
* @param $methodName The name of the method being invoked.
60+
* @param $args The arguments to be passed to the sequential method.
6061
*
61-
* @param array $definition The data that dictates how the operation works
62+
* @throws \RuntimeException If method does not exist
6263
*
63-
* @return Operation
64+
* @return Promise
65+
*/
66+
public function __call($methodName, $args)
67+
{
68+
$e = function ($name) {
69+
return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name));
70+
};
71+
72+
if (substr($methodName, -5) === 'Async') {
73+
$realMethod = substr($methodName, 0, -5);
74+
if (!method_exists($this, $realMethod)) {
75+
throw $e($realMethod);
76+
}
77+
78+
$promise = new Promise(
79+
function () use (&$promise, $realMethod, $args) {
80+
$value = call_user_func_array([$this, $realMethod], $args);
81+
$promise->resolve($value);
82+
}
83+
);
84+
85+
return $promise;
86+
}
87+
88+
throw $e($methodName);
89+
}
90+
91+
/**
92+
* {@inheritdoc}
6493
*/
6594
public function getOperation(array $definition): Operation
6695
{
6796
return new Operation($definition);
6897
}
6998

99+
/**
100+
* @param Operation $operation
101+
* @param array $userValues
102+
* @param bool $async
103+
*
104+
* @return mixed
105+
* @throws \Exception
106+
*/
70107
protected function sendRequest(Operation $operation, array $userValues = [], bool $async = false)
71108
{
72109
$operation->validate($userValues);
@@ -100,76 +137,16 @@ public function executeAsync(array $definition, array $userValues = []): Promise
100137
public function model(string $class, $data = null): ResourceInterface
101138
{
102139
$model = new $class($this->client, $this->api);
103-
104140
// @codeCoverageIgnoreStart
105141
if (!$model instanceof ResourceInterface) {
106142
throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class));
107143
}
108144
// @codeCoverageIgnoreEnd
109-
110145
if ($data instanceof ResponseInterface) {
111146
$model->populateFromResponse($data);
112147
} elseif (is_array($data)) {
113148
$model->populateFromArray($data);
114149
}
115-
116150
return $model;
117151
}
118-
119-
/**
120-
* Will create a new instance of this class with the current HTTP client and API injected in. This
121-
* is useful when enumerating over a collection since multiple copies of the same resource class
122-
* are needed.
123-
*
124-
* @return static
125-
*/
126-
public function newInstance(): self
127-
{
128-
return new static($this->client, $this->api);
129-
}
130-
131-
/**
132-
* @return \GuzzleHttp\Psr7\Uri:null
133-
*/
134-
protected function getHttpBaseUrl()
135-
{
136-
return $this->client->getConfig('base_uri');
137-
}
138-
139-
/**
140-
* Magic method which intercepts async calls, finds the sequential version, and wraps it in a
141-
* {@see Promise} object. In order for this to happen, the called methods need to be in the
142-
* following format: `createAsync`, where `create` is the sequential method being wrapped.
143-
*
144-
* @param $methodName The name of the method being invoked.
145-
* @param $args The arguments to be passed to the sequential method.
146-
*
147-
* @throws \RuntimeException If method does not exist
148-
*
149-
* @return Promise
150-
*/
151-
public function __call($methodName, $args)
152-
{
153-
$e = function ($name) {
154-
return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name));
155-
};
156-
157-
if (substr($methodName, -5) === 'Async') {
158-
$realMethod = substr($methodName, 0, -5);
159-
if (!method_exists($this, $realMethod)) {
160-
throw $e($realMethod);
161-
}
162-
163-
$promise = new Promise(
164-
function () use (&$promise, $realMethod, $args) {
165-
$value = call_user_func_array([$this, $realMethod], $args);
166-
$promise->resolve($value);
167-
}
168-
);
169-
170-
return $promise;
171-
}
172-
173-
throw $e($methodName);
174-
}
175152
}

src/Common/Api/Parameter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ private function validateObject($userValues)
240240
/**
241241
* Internal method which retrieves a nested property for object parameters.
242242
*
243-
* @param $key The name of the child parameter
243+
* @param string $key The name of the child parameter
244244
*
245245
* @returns Parameter
246246
* @throws \Exception

src/Common/HydratorStrategyTrait.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ trait HydratorStrategyTrait
1515
* @param array $data The data to set
1616
* @param array $aliases Any aliases
1717
*/
18-
private function hydrate(array $data, array $aliases = [])
18+
public function hydrate(array $data, array $aliases = [])
1919
{
2020
foreach ($data as $key => $val) {
2121
$key = isset($aliases[$key]) ? $aliases[$key] : $key;
2222
if (property_exists($this, $key)) {
23-
$this->$key = $val;
23+
$this->{$key} = $val;
2424
}
2525
}
2626
}
2727

28-
private function set(string $key, $property, array $data, callable $fn = null)
28+
public function set(string $key, $property, array $data, callable $fn = null)
2929
{
3030
if (isset($data[$key]) && property_exists($this, $property)) {
3131
$value = $fn ? call_user_func($fn, $data[$key]) : $data[$key];

src/Common/Resource/AbstractResource.php

Lines changed: 30 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace OpenCloud\Common\Resource;
44

5-
use OpenCloud\Common\Api\Operator;
5+
use OpenCloud\Common\Transport\Serializable;
66
use OpenCloud\Common\Transport\Utils;
77
use Psr\Http\Message\ResponseInterface;
88

@@ -13,10 +13,8 @@
1313
*
1414
* @package OpenCloud\Common\Resource
1515
*/
16-
abstract class AbstractResource extends Operator implements ResourceInterface
16+
abstract class AbstractResource implements ResourceInterface, Serializable
1717
{
18-
const DEFAULT_MARKER_KEY = 'id';
19-
2018
/**
2119
* The JSON key that indicates how the API nests singular resources. For example, when
2220
* performing a GET, it could respond with ``{"server": {"id": "12345"}}``. In this case,
@@ -26,22 +24,6 @@ abstract class AbstractResource extends Operator implements ResourceInterface
2624
*/
2725
protected $resourceKey;
2826

29-
/**
30-
* The key that indicates how the API nests resource collections. For example, when
31-
* performing a GET, it could respond with ``{"servers": [{}, {}]}``. In this case, "servers"
32-
* is the resources key, since the array of servers is nested inside.
33-
*
34-
* @var string
35-
*/
36-
protected $resourcesKey;
37-
38-
/**
39-
* Indicates which attribute of the current resource should be used for pagination markers.
40-
*
41-
* @var string
42-
*/
43-
protected $markerKey;
44-
4527
/**
4628
* An array of aliases that will be checked when the resource is being populated. For example,
4729
*
@@ -58,7 +40,7 @@ abstract class AbstractResource extends Operator implements ResourceInterface
5840
*
5941
* @param ResponseInterface $response
6042
*
61-
* @return $this|ResourceInterface
43+
* @return AbstractResource
6244
*/
6345
public function populateFromResponse(ResponseInterface $response): self
6446
{
@@ -166,80 +148,46 @@ protected function getAttrs(array $keys)
166148
return $output;
167149
}
168150

169-
/**
170-
* @param array $definition
171-
*
172-
* @return mixed
173-
*/
174-
public function executeWithState(array $definition)
151+
public function model(string $class, $data = null): ResourceInterface
175152
{
176-
return $this->execute($definition, $this->getAttrs(array_keys($definition['params'])));
177-
}
153+
$model = new $class();
178154

179-
private function getResourcesKey(): string
180-
{
181-
$resourcesKey = $this->resourcesKey;
155+
// @codeCoverageIgnoreStart
156+
if (!$model instanceof ResourceInterface) {
157+
throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class));
158+
}
159+
// @codeCoverageIgnoreEnd
182160

183-
if (!$resourcesKey) {
184-
$class = substr(static::class, strrpos(static::class, '\\') + 1);
185-
$resourcesKey = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $class)) . 's';
161+
if ($data instanceof ResponseInterface) {
162+
$model->populateFromResponse($data);
163+
} elseif (is_array($data)) {
164+
$model->populateFromArray($data);
186165
}
187166

188-
return $resourcesKey;
167+
return $model;
189168
}
190169

191-
/**
192-
* {@inheritDoc}
193-
*/
194-
public function enumerate(array $def, array $userVals = [], callable $mapFn = null): \Generator
170+
public function serialize(): \stdClass
195171
{
196-
$operation = $this->getOperation($def);
172+
$output = new \stdClass();
197173

198-
$requestFn = function ($marker) use ($operation, $userVals) {
199-
if ($marker) {
200-
$userVals['marker'] = $marker;
201-
}
202-
return $this->sendRequest($operation, $userVals);
203-
};
204-
205-
$resourceFn = function (array $data) {
206-
$resource = $this->newInstance();
207-
$resource->populateFromArray($data);
208-
return $resource;
209-
};
210-
211-
$opts = [
212-
'limit' => isset($userVals['limit']) ? $userVals['limit'] : null,
213-
'resourcesKey' => $this->getResourcesKey(),
214-
'markerKey' => $this->markerKey,
215-
'mapFn' => $mapFn,
216-
];
217-
218-
$iterator = new Iterator($opts, $requestFn, $resourceFn);
219-
return $iterator();
220-
}
174+
foreach ((new \ReflectionClass($this))->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
175+
$name = $property->getName();
176+
$val = $this->{$name};
221177

222-
public function extractMultipleInstances(ResponseInterface $response, string $key = null): array
223-
{
224-
$key = $key ?: $this->getResourcesKey();
225-
$resourcesData = Utils::jsonDecode($response)[$key];
178+
$fn = function ($val) {
179+
return ($val instanceof Serializable) ? $val->serialize() : $val;
180+
};
226181

227-
$resources = [];
182+
if (is_array($val)) {
183+
foreach ($val as $sk => $sv) {
184+
$val[$sk] = $fn($sv);
185+
}
186+
}
228187

229-
foreach ($resourcesData as $resourceData) {
230-
$resource = $this->newInstance();
231-
$resource->populateFromArray($resourceData);
232-
$resources[] = $resource;
188+
$output->{$name} = $fn($val);
233189
}
234190

235-
return $resources;
236-
}
237-
238-
protected function getService()
239-
{
240-
$class = static::class;
241-
$service = substr($class, 0, strpos($class, 'Models') - 1) . '\\Service';
242-
243-
return new $service($this->client, $this->api);
191+
return $output;
244192
}
245193
}

0 commit comments

Comments
 (0)