Completed
Push — master ( a1068d...c410a5 )
by David
10s
created

Service   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 129
dl 0
loc 441
rs 5.5199
c 0
b 0
f 0
wmc 56

35 Methods

Rating   Name   Duplication   Size   Complexity  
A jsonSerialize() 0 31 3
A parsePayload() 0 26 6
A __construct() 0 3 1
A addPort() 0 5 1
A getPorts() 0 3 1
A getImage() 0 3 1
A getCommand() 0 3 1
A getDockerfileCommands() 0 3 1
A getEnvironment() 0 3 1
A getServiceName() 0 3 1
A getLabels() 0 3 1
A addTmpfsVolume() 0 3 1
A setInternalPorts() 0 3 1
A addEnvVar() 0 17 5
A addVolume() 0 14 4
A addDependsOn() 0 3 1
A addCommand() 0 3 1
A setImage() 0 3 1
A addBindVolume() 0 3 1
A getInternalPorts() 0 3 1
A addNamedVolume() 0 3 1
A getVolumes() 0 3 1
A setCommand() 0 3 1
A addLabel() 0 4 1
A checkValidity() 0 13 3
A addSharedSecret() 0 3 1
A getDependsOn() 0 3 1
A addSharedEnvVariable() 0 3 1
A setServiceName() 0 3 1
A setDependsOn() 0 3 1
A addInternalPort() 0 3 1
A addContainerEnvVariable() 0 3 1
A imageJsonSerialize() 0 22 6
A addDockerfileCommand() 0 3 1
A addImageEnvVariable() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Service often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Service, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace TheAentMachine\Service;
4
5
use Opis\JsonSchema\ValidationError;
6
use Opis\JsonSchema\Validator;
7
use TheAentMachine\Service\Enum\EnvVariableTypeEnum;
8
use TheAentMachine\Service\Enum\VolumeTypeEnum;
9
use TheAentMachine\Service\Environment\EnvVariable;
10
use TheAentMachine\Service\Exception\ServiceException;
11
use TheAentMachine\Service\Volume\BindVolume;
12
use TheAentMachine\Service\Volume\NamedVolume;
13
use TheAentMachine\Service\Volume\TmpfsVolume;
14
15
class Service implements \JsonSerializable
16
{
17
    /** @var string */
18
    private $serviceName = '';
19
    /** @var string|null */
20
    private $image = null;
21
    /** @var string[] */
22
    private $command = [];
23
    /** @var int[] */
24
    private $internalPorts = [];
25
    /** @var string[] */
26
    private $dependsOn = [];
27
    /** @var mixed[] */
28
    private $ports = [];
29
    /** @var mixed[] */
30
    private $labels = [];
31
    /** @var mixed[] */
32
    private $environment = [];
33
    /** @var mixed[] */
34
    private $volumes = [];
35
    /** @var \stdClass */
36
    private $validatorSchema;
37
    /** @var string[] */
38
    private $dockerfileCommands = [];
39
40
    /**
41
     * Service constructor.
42
     */
43
    public function __construct()
44
    {
45
        $this->validatorSchema = json_decode((string)file_get_contents(__DIR__ . '/ServiceJsonSchema.json'), false);
46
    }
47
48
    /**
49
     * @param mixed[] $payload
50
     * @return Service
51
     * @throws ServiceException
52
     */
53
    public static function parsePayload(array $payload): Service
54
    {
55
        $service = new self();
56
        $service->checkValidity($payload);
57
        $service->serviceName = $payload['serviceName'] ?? '';
58
        $s = $payload['service'] ?? [];
59
        if (!empty($s)) {
60
            $service->image = $s['image'] ?? null;
61
            $service->command = $s['command'] ?? [];
62
            $service->internalPorts = $s['internalPorts'] ?? [];
63
            $service->dependsOn = $s['dependsOn'] ?? [];
64
            $service->ports = $s['ports'] ?? [];
65
            $service->labels = $s['labels'] ?? [];
66
            if (!empty($s['environment'])) {
67
                foreach ($s['environment'] as $key => $env) {
68
                    $service->addEnvVar($key, $env['value'], $env['type']);
69
                }
70
            }
71
            if (!empty($s['volumes'])) {
72
                foreach ($s['volumes'] as $vol) {
73
                    $service->addVolume($vol['type'], $vol['source'], $vol['target'] ?? '', $vol['readOnly'] ?? false);
74
                }
75
            }
76
        }
77
        $service->dockerfileCommands = $payload['dockerfileCommands'] ?? [];
78
        return $service;
79
    }
80
81
    /**
82
     * Specify data which should be serialized to JSON
83
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
84
     * @return array data which can be serialized by <b>json_encode</b>,
85
     * which is a value of any type other than a resource.
86
     * @since 5.4.0
87
     * @throws ServiceException
88
     */
89
    public function jsonSerialize(): array
90
    {
91
        $jsonSerializeMap = function (\JsonSerializable $obj): array {
92
            return $obj->jsonSerialize();
93
        };
94
95
        $json = array(
96
            'serviceName' => $this->serviceName,
97
        );
98
99
        $service = array_filter([
100
            'image' => $this->image,
101
            'command' => $this->command,
102
            'internalPorts' => $this->internalPorts,
103
            'dependsOn' => $this->dependsOn,
104
            'ports' => $this->ports,
105
            'labels' => $this->labels,
106
            'environment' => array_map($jsonSerializeMap, $this->environment),
107
            'volumes' => array_map($jsonSerializeMap, $this->volumes),
108
        ]);
109
110
        if (!empty($service)) {
111
            $json['service'] = $service;
112
        }
113
114
        if (!empty($this->dockerfileCommands)) {
115
            $json['dockerfileCommands'] = $this->dockerfileCommands;
116
        }
117
118
        $this->checkValidity($json);
119
        return $json;
120
    }
121
122
    /**
123
     * @return mixed[]
124
     */
125
    public function imageJsonSerialize(): array
126
    {
127
        $dockerfileCommands = [];
128
        $dockerfileCommands[] = 'FROM ' . $this->image;
129
        foreach ($this->environment as $key => $env) {
130
            if ($env->getType() === EnvVariableTypeEnum::IMAGE_ENV_VARIABLE) {
131
                $dockerfileCommands[] = "ENV $key" . '='. $env->getValue();
132
            }
133
        }
134
        foreach ($this->volumes as $volume) {
135
            if ($volume->getType() === VolumeTypeEnum::BIND_VOLUME) {
136
                $dockerfileCommands[] = 'COPY ' . $volume->getSource() . ' ' . $volume->getTarget();
137
            }
138
        }
139
140
        if (!empty($this->command)) {
141
            $dockerfileCommands[] = 'CMD ' . implode(' ', $this->command);
142
        }
143
144
        $dockerfileCommands = array_merge($dockerfileCommands, $this->dockerfileCommands);
145
146
        return $dockerfileCommands;
147
    }
148
149
    /**
150
     * @param \stdClass|array|string $data
151
     * @return bool
152
     * @throws ServiceException
153
     */
154
    private function checkValidity($data): bool
155
    {
156
        if (\is_array($data)) {
157
            $data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($data), false);
158
        }
159
        $validator = new Validator();
160
        $result = $validator->dataValidation($data, $this->validatorSchema);
161
        if (!$result->isValid()) {
162
            /** @var ValidationError $vError */
163
            $vError = $result->getFirstError();
164
            throw ServiceException::invalidServiceData($vError);
165
        }
166
        return $result->isValid();
167
    }
168
169
    /**
170
     * @return string
171
     */
172
    public function getServiceName(): string
173
    {
174
        return $this->serviceName;
175
    }
176
177
    /**
178
     * @return string|null
179
     */
180
    public function getImage(): ?string
181
    {
182
        return $this->image;
183
    }
184
185
    /**
186
     * @return string[]
187
     */
188
    public function getCommand(): array
189
    {
190
        return $this->command;
191
    }
192
193
    /**
194
     * @return int[]
195
     */
196
    public function getInternalPorts(): array
197
    {
198
        return $this->internalPorts;
199
    }
200
201
    /**
202
     * @return string[]
203
     */
204
    public function getDependsOn(): array
205
    {
206
        return $this->dependsOn;
207
    }
208
209
    /**
210
     * @return mixed[]
211
     */
212
    public function getPorts(): array
213
    {
214
        return $this->ports;
215
    }
216
217
    /**
218
     * @return mixed[]
219
     */
220
    public function getLabels(): array
221
    {
222
        return $this->labels;
223
    }
224
225
    /**
226
     * @return mixed[]
227
     */
228
    public function getEnvironment(): array
229
    {
230
        return $this->environment;
231
    }
232
233
    /**
234
     * @return mixed[]
235
     */
236
    public function getVolumes(): array
237
    {
238
        return $this->volumes;
239
    }
240
241
    /**
242
     * @return string[]
243
     */
244
    public function getDockerfileCommands(): array
245
    {
246
        return $this->dockerfileCommands;
247
    }
248
249
    /**
250
     * @param string $serviceName
251
     */
252
    public function setServiceName(string $serviceName): void
253
    {
254
        $this->serviceName = $serviceName;
255
    }
256
257
    /**
258
     * @param string|null $image
259
     */
260
    public function setImage(?string $image): void
261
    {
262
        $this->image = $image;
263
    }
264
265
    /**
266
     * @param string[] $command
267
     */
268
    public function setCommand(array $command): void
269
    {
270
        $this->command = $command;
271
    }
272
273
    /**
274
     * @param int[] $internalPorts
275
     */
276
    public function setInternalPorts(array $internalPorts): void
277
    {
278
        $this->internalPorts = $internalPorts;
279
    }
280
281
    /**
282
     * @param string[] $dependsOn
283
     */
284
    public function setDependsOn(array $dependsOn): void
285
    {
286
        $this->dependsOn = $dependsOn;
287
    }
288
289
    /**
290
     * @param string $command
291
     */
292
    public function addCommand(string $command): void
293
    {
294
        $this->command[] = $command;
295
    }
296
297
    /**
298
     * @param int $internalPort
299
     */
300
    public function addInternalPort(int $internalPort): void
301
    {
302
        $this->internalPorts[] = $internalPort;
303
    }
304
305
    /**
306
     * @param string $dependsOn
307
     */
308
    public function addDependsOn(string $dependsOn): void
309
    {
310
        $this->dependsOn[] = $dependsOn;
311
    }
312
313
    /**
314
     * @param int $source
315
     * @param int $target
316
     */
317
    public function addPort(int $source, int $target): void
318
    {
319
        $this->ports[] = array(
320
            'source' => $source,
321
            'target' => $target,
322
        );
323
    }
324
325
    /**
326
     * @param string $key
327
     * @param string $value
328
     */
329
    public function addLabel(string $key, string $value): void
330
    {
331
        $this->labels[$key] = array(
332
            'value' => $value,
333
        );
334
    }
335
336
    /**
337
     * @param string $key
338
     * @param string $value
339
     * @param string $type
340
     * @throws ServiceException
341
     */
342
    private function addEnvVar(string $key, string $value, string $type): void
343
    {
344
        switch ($type) {
345
            case EnvVariableTypeEnum::SHARED_ENV_VARIABLE:
346
                $this->addSharedEnvVariable($key, $value);
347
                break;
348
            case EnvVariableTypeEnum::SHARED_SECRET:
349
                $this->addSharedSecret($key, $value);
350
                break;
351
            case EnvVariableTypeEnum::IMAGE_ENV_VARIABLE:
352
                $this->addImageEnvVariable($key, $value);
353
                break;
354
            case EnvVariableTypeEnum::CONTAINER_ENV_VARIABLE:
355
                $this->addContainerEnvVariable($key, $value);
356
                break;
357
            default:
358
                throw ServiceException::unknownEnvVariableType($type);
359
        }
360
    }
361
362
    /**
363
     * @param string $key
364
     * @param string $value
365
     */
366
    public function addSharedEnvVariable(string $key, string $value): void
367
    {
368
        $this->environment[$key] = new EnvVariable($value, 'sharedEnvVariable');
369
    }
370
371
    /**
372
     * @param string $key
373
     * @param string $value
374
     */
375
    public function addSharedSecret(string $key, string $value): void
376
    {
377
        $this->environment[$key] = new EnvVariable($value, 'sharedSecret');
378
    }
379
380
    /**
381
     * @param string $key
382
     * @param string $value
383
     */
384
    public function addImageEnvVariable(string $key, string $value): void
385
    {
386
        $this->environment[$key] = new EnvVariable($value, 'imageEnvVariable');
387
    }
388
389
    /**
390
     * @param string $key
391
     * @param string $value
392
     */
393
    public function addContainerEnvVariable(string $key, string $value): void
394
    {
395
        $this->environment[$key] = new EnvVariable($value, 'containerEnvVariable');
396
    }
397
398
    /**
399
     * @param string $type
400
     * @param string $source
401
     * @param string $target
402
     * @param bool $readOnly
403
     * @throws ServiceException
404
     */
405
    private function addVolume(string $type, string $source, string $target = '', bool $readOnly = false): void
406
    {
407
        switch ($type) {
408
            case VolumeTypeEnum::NAMED_VOLUME:
409
                $this->addNamedVolume($source, $target, $readOnly);
410
                break;
411
            case VolumeTypeEnum::BIND_VOLUME:
412
                $this->addBindVolume($source, $target, $readOnly);
413
                break;
414
            case VolumeTypeEnum::TMPFS_VOLUME:
415
                $this->addTmpfsVolume($source);
416
                break;
417
            default:
418
                throw ServiceException::unknownVolumeType($type);
419
        }
420
    }
421
422
    /**
423
     * @param string $source
424
     * @param string $target
425
     * @param bool $readOnly
426
     */
427
    public function addNamedVolume(string $source, string $target, bool $readOnly = false): void
428
    {
429
        $this->volumes[] = new NamedVolume($source, $target, $readOnly);
430
    }
431
432
    /**
433
     * @param string $source
434
     * @param string $target
435
     * @param bool $readOnly
436
     */
437
    public function addBindVolume(string $source, string $target, bool $readOnly = false): void
438
    {
439
        $this->volumes[] = new BindVolume($source, $target, $readOnly);
440
    }
441
442
    /**
443
     * @param string $source
444
     */
445
    public function addTmpfsVolume(string $source): void
446
    {
447
        $this->volumes[] = new TmpfsVolume($source);
448
    }
449
450
    /**
451
     * @param string $dockerfileCommand
452
     */
453
    public function addDockerfileCommand(string $dockerfileCommand): void
454
    {
455
        $this->dockerfileCommands[] = $dockerfileCommand;
456
    }
457
}
458