|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace OpenStack\Common\Api; |
|
4
|
|
|
|
|
5
|
|
|
use function GuzzleHttp\uri_template; |
|
6
|
|
|
use GuzzleHttp\ClientInterface; |
|
7
|
|
|
use GuzzleHttp\Promise\Promise; |
|
8
|
|
|
use OpenStack\Common\Resource\ResourceInterface; |
|
9
|
|
|
use OpenStack\Common\Transport\RequestSerializer; |
|
10
|
|
|
use Psr\Http\Message\ResponseInterface; |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* {@inheritDoc} |
|
14
|
|
|
*/ |
|
15
|
|
|
abstract class Operator implements OperatorInterface |
|
16
|
|
|
{ |
|
17
|
|
|
/** @var ClientInterface */ |
|
18
|
|
|
private $client; |
|
19
|
|
|
|
|
20
|
|
|
/** @var ApiInterface */ |
|
21
|
|
|
protected $api; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* {@inheritDoc} |
|
25
|
|
|
*/ |
|
26
|
214 |
|
public function __construct(ClientInterface $client, ApiInterface $api) |
|
27
|
|
|
{ |
|
28
|
214 |
|
$this->client = $client; |
|
29
|
214 |
|
$this->api = $api; |
|
30
|
214 |
|
} |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* Magic method for dictating how objects are rendered when var_dump is called. |
|
34
|
|
|
* For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are |
|
35
|
|
|
* removed to provide easier access to normal state, such as resource attributes. |
|
36
|
|
|
* |
|
37
|
|
|
* @return array |
|
38
|
|
|
*/ |
|
39
|
|
|
public function __debugInfo() |
|
40
|
|
|
{ |
|
41
|
|
|
$excludedVars = ['client', 'errorBuilder', 'api']; |
|
42
|
|
|
|
|
43
|
|
|
$output = []; |
|
44
|
|
|
|
|
45
|
|
|
foreach (get_object_vars($this) as $key => $val) { |
|
46
|
|
|
if (!in_array($key, $excludedVars)) { |
|
47
|
|
|
$output[$key] = $val; |
|
48
|
|
|
} |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
return $output; |
|
52
|
|
|
} |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* Retrieves a populated Operation according to the definition and values provided. A |
|
56
|
|
|
* HTTP client is also injected into the object to allow it to communicate with the remote API. |
|
57
|
|
|
* |
|
58
|
|
|
* @param array $definition The data that dictates how the operation works |
|
59
|
|
|
* |
|
60
|
|
|
* @return Operation |
|
61
|
|
|
*/ |
|
62
|
165 |
|
public function getOperation(array $definition) |
|
63
|
|
|
{ |
|
64
|
165 |
|
return new Operation($definition); |
|
65
|
|
|
} |
|
66
|
|
|
|
|
67
|
164 |
|
protected function sendRequest(Operation $operation, array $userValues = [], $async = false) |
|
68
|
|
|
{ |
|
69
|
164 |
|
$uri = uri_template($operation->getPath(), $userValues); |
|
70
|
164 |
|
$options = RequestSerializer::serializeOptions($operation, $userValues); |
|
71
|
164 |
|
$method = $async ? 'requestAsync' : 'request'; |
|
72
|
|
|
|
|
73
|
164 |
|
return $this->client->$method($operation->getMethod(), $uri, $options); |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* {@inheritDoc} |
|
78
|
|
|
*/ |
|
79
|
|
|
public function executeAsync(array $definition, array $userValues = []) |
|
80
|
|
|
{ |
|
81
|
|
|
return $this->sendRequest($this->getOperation($definition), $userValues, true); |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
138 |
|
public function execute(array $definition, array $userValues = []) |
|
85
|
|
|
{ |
|
86
|
138 |
|
return $this->sendRequest($this->getOperation($definition), $userValues); |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
/** |
|
90
|
|
|
* {@inheritDoc} |
|
91
|
|
|
*/ |
|
92
|
80 |
|
public function model($class, $data = null) |
|
93
|
|
|
{ |
|
94
|
80 |
|
$model = new $class($this->client, $this->api); |
|
95
|
|
|
|
|
96
|
|
|
// @codeCoverageIgnoreStart |
|
97
|
|
|
if (!$model instanceof ResourceInterface) { |
|
98
|
|
|
throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class)); |
|
99
|
|
|
} |
|
100
|
|
|
// @codeCoverageIgnoreEnd |
|
101
|
|
|
|
|
102
|
80 |
|
if ($data instanceof ResponseInterface) { |
|
103
|
2 |
|
$model->populateFromResponse($data); |
|
104
|
80 |
|
} elseif (is_array($data)) { |
|
105
|
23 |
|
$model->populateFromArray($data); |
|
106
|
23 |
|
} |
|
107
|
|
|
|
|
108
|
80 |
|
return $model; |
|
|
|
|
|
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Will create a new instance of this class with the current HTTP client and API injected in. This |
|
113
|
|
|
* is useful when enumerating over a collection since multiple copies of the same resource class |
|
114
|
|
|
* are needed. |
|
115
|
|
|
* |
|
116
|
|
|
* @return static |
|
117
|
|
|
*/ |
|
118
|
30 |
|
public function newInstance() |
|
119
|
|
|
{ |
|
120
|
30 |
|
return new static($this->client, $this->api); |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
/** |
|
124
|
|
|
* @return \GuzzleHttp\Psr7\Uri |
|
125
|
|
|
*/ |
|
126
|
1 |
|
protected function getHttpBaseUrl() |
|
127
|
|
|
{ |
|
128
|
1 |
|
return $this->client->getConfig('base_url'); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* Magic method which intercepts async calls, finds the sequential version, and wraps it in a |
|
133
|
|
|
* {@see Promise} object. In order for this to happen, the called methods need to be in the |
|
134
|
|
|
* following format: `createAsync`, where `create` is the sequential method being wrapped. |
|
135
|
|
|
* |
|
136
|
|
|
* @param $methodName The name of the method being invoked. |
|
137
|
|
|
* @param $args The arguments to be passed to the sequential method. |
|
138
|
|
|
* |
|
139
|
|
|
* @return Promise |
|
140
|
|
|
*/ |
|
141
|
2 |
|
public function __call($methodName, $args) |
|
142
|
|
|
{ |
|
143
|
2 |
|
if (substr($methodName, -5) === 'Async') { |
|
144
|
2 |
|
$realMethod = substr($methodName, 0, -5); |
|
145
|
2 |
|
if (!method_exists($this, $realMethod)) { |
|
146
|
1 |
|
throw new \InvalidArgumentException(sprintf( |
|
147
|
1 |
|
'%s is not a defined method on %s', $realMethod, get_class($this) |
|
148
|
1 |
|
)); |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
1 |
|
$promise = new Promise( |
|
152
|
|
|
function () use (&$promise, $realMethod, $args) { |
|
153
|
1 |
|
$value = call_user_func_array([$this, $realMethod], $args); |
|
154
|
1 |
|
$promise->resolve($value); |
|
155
|
1 |
|
}, |
|
156
|
|
|
function ($e) use (&$promise) { |
|
157
|
|
|
$promise->reject($e); |
|
158
|
|
|
} |
|
159
|
1 |
|
); |
|
160
|
|
|
|
|
161
|
1 |
|
return $promise; |
|
162
|
|
|
} |
|
163
|
|
|
} |
|
164
|
|
|
} |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.