1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace OpenStack\Common\Service; |
4
|
|
|
|
5
|
|
|
use GuzzleHttp\Client; |
6
|
|
|
use GuzzleHttp\ClientInterface; |
7
|
|
|
use GuzzleHttp\Middleware as GuzzleMiddleware; |
8
|
|
|
use OpenStack\Common\Auth\IdentityService; |
9
|
|
|
use OpenStack\Common\Auth\Token; |
10
|
|
|
use OpenStack\Common\Transport\HandlerStack; |
11
|
|
|
use OpenStack\Common\Transport\Middleware; |
12
|
|
|
use OpenStack\Common\Transport\Utils; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* A Builder for easily creating OpenStack services. |
16
|
|
|
* |
17
|
|
|
* @package OpenStack\Common\Service |
18
|
|
|
*/ |
19
|
|
|
class Builder |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* Global options that will be applied to every service created by this builder. |
23
|
|
|
* |
24
|
|
|
* @var array |
25
|
|
|
*/ |
26
|
|
|
private $globalOptions = []; |
27
|
|
|
|
28
|
|
|
/** @var string */ |
29
|
|
|
private $rootNamespace; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Defaults that will be applied to options if no values are provided by the user. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
private $defaults = ['urlType' => 'publicURL']; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param array $globalOptions Options that will be applied to every service created by this builder. |
40
|
|
|
* Eventually they will be merged (and if necessary overridden) by the |
41
|
9 |
|
* service-specific options passed in. |
42
|
|
|
* @param string $rootNamespace API classes' root namespace |
43
|
9 |
|
*/ |
44
|
9 |
|
public function __construct(array $globalOptions = [], $rootNamespace = 'OpenStack') |
45
|
|
|
{ |
46
|
|
|
$this->globalOptions = $globalOptions; |
47
|
|
|
$this->rootNamespace = $rootNamespace; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
private function getClasses($namespace) |
51
|
|
|
{ |
52
|
|
|
$namespace = $this->rootNamespace . '\\' . $namespace; |
53
|
|
|
$classes = [$namespace.'\\Api', $namespace.'\\Service']; |
54
|
2 |
|
|
55
|
|
|
foreach ($classes as $class) { |
56
|
2 |
|
if (!class_exists($class)) { |
57
|
|
|
throw new \RuntimeException(sprintf("%s does not exist", $class)); |
58
|
|
|
} |
59
|
2 |
|
} |
60
|
2 |
|
|
61
|
2 |
|
return $classes; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* This method will return an OpenStack service ready fully built and ready for use. There is |
66
|
|
|
* some initial setup that may prohibit users from directly instantiating the service class |
67
|
|
|
* directly - this setup includes the configuration of the HTTP client's base URL, and the |
68
|
|
|
* attachment of an authentication handler. |
69
|
|
|
* |
70
|
|
|
* @param string $namespace The namespace of the service |
71
|
|
|
* @param array $serviceOptions The service-specific options to use |
72
|
|
|
* |
73
|
|
|
* @return \OpenStack\Common\Service\ServiceInterface |
74
|
|
|
* |
75
|
|
|
* @throws \Exception |
76
|
|
|
*/ |
77
|
|
|
public function createService(string $namespace, array $serviceOptions = []): ServiceInterface |
78
|
9 |
|
{ |
79
|
|
|
$options = $this->mergeOptions($serviceOptions); |
80
|
9 |
|
|
81
|
|
|
$this->stockAuthHandler($options); |
82
|
6 |
|
$this->stockHttpClient($options, $namespace); |
83
|
6 |
|
|
84
|
6 |
|
list($apiClass, $serviceClass) = $this->getClasses($namespace); |
85
|
|
|
|
86
|
2 |
|
return new $serviceClass($options['httpClient'], new $apiClass()); |
87
|
|
|
} |
88
|
2 |
|
|
89
|
|
|
private function stockHttpClient(array &$options, string $serviceName) |
90
|
|
|
{ |
91
|
6 |
|
if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) { |
92
|
|
|
if (stripos($serviceName, 'identity') !== false) { |
93
|
6 |
|
$baseUrl = $options['authUrl']; |
94
|
6 |
|
$stack = $this->getStack($options['authHandler']); |
95
|
1 |
|
} else { |
96
|
1 |
|
list($token, $baseUrl) = $options['identityService']->authenticate($options); |
97
|
1 |
|
$stack = $this->getStack($options['authHandler'], $token); |
98
|
5 |
|
} |
99
|
1 |
|
|
100
|
|
|
$microVersion = $options['microVersion'] ?? null; |
101
|
|
|
|
102
|
2 |
|
$this->addDebugMiddleware($options, $stack); |
103
|
|
|
|
104
|
2 |
|
$options['httpClient'] = $this->httpClient($baseUrl, $stack, $options['catalogType'], $microVersion); |
105
|
2 |
|
} |
106
|
2 |
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @codeCoverageIgnore |
110
|
|
|
*/ |
111
|
|
|
private function addDebugMiddleware(array $options, HandlerStack &$stack) |
112
|
|
|
{ |
113
|
|
View Code Duplication |
if (!empty($options['debugLog']) |
|
|
|
|
114
|
|
|
&& !empty($options['logger']) |
115
|
|
|
&& !empty($options['messageFormatter']) |
116
|
|
|
) { |
117
|
|
|
$stack->push(GuzzleMiddleware::log($options['logger'], $options['messageFormatter'])); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
6 |
|
/** |
122
|
|
|
* @param array $options |
123
|
6 |
|
* |
124
|
5 |
|
* @codeCoverageIgnore |
125
|
5 |
|
*/ |
126
|
5 |
|
private function stockAuthHandler(array &$options) |
127
|
6 |
|
{ |
128
|
|
|
if (!isset($options['authHandler'])) { |
129
|
|
|
$options['authHandler'] = function () use ($options) { |
130
|
|
|
return $options['identityService']->authenticate($options)[0]; |
131
|
|
|
}; |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
private function getStack(callable $authHandler, Token $token = null): HandlerStack |
136
|
|
|
{ |
137
|
|
|
$stack = HandlerStack::create(); |
138
|
|
|
$stack->push(Middleware::authHandler($authHandler, $token)); |
139
|
|
|
return $stack; |
140
|
|
|
} |
141
|
|
|
|
142
|
2 |
|
private function httpClient(string $baseUrl, HandlerStack $stack, string $serviceType = null, string $microVersion = null): ClientInterface |
143
|
|
|
{ |
144
|
2 |
|
$clientOptions = [ |
145
|
2 |
|
'base_uri' => Utils::normalizeUrl($baseUrl), |
146
|
2 |
|
'handler' => $stack, |
147
|
|
|
]; |
148
|
|
|
|
149
|
6 |
|
if ($microVersion && $serviceType) { |
|
|
|
|
150
|
|
|
$clientOptions['headers']['OpenStack-API-Version'] = sprintf('%s %s', $serviceType, $microVersion); |
151
|
6 |
|
} |
152
|
6 |
|
|
153
|
6 |
|
if (isset($this->globalOptions['requestOptions'])) { |
154
|
6 |
|
$clientOptions = array_merge($this->globalOptions['requestOptions'], $clientOptions); |
155
|
|
|
} |
156
|
|
|
return new Client($clientOptions); |
157
|
9 |
|
} |
158
|
|
|
|
159
|
9 |
|
private function mergeOptions(array $serviceOptions): array |
160
|
|
|
{ |
161
|
9 |
|
$options = array_merge($this->defaults, $this->globalOptions, $serviceOptions); |
162
|
3 |
|
|
163
|
|
|
if (!isset($options['authUrl'])) { |
164
|
|
|
throw new \InvalidArgumentException('"authUrl" is a required option'); |
165
|
6 |
|
} |
166
|
|
|
|
167
|
|
|
if (!isset($options['identityService']) || !($options['identityService'] instanceof IdentityService)) { |
168
|
|
|
throw new \InvalidArgumentException(sprintf( |
169
|
|
|
'"identityService" must be specified and implement %s', IdentityService::class |
170
|
|
|
)); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $options; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.