Completed
Pull Request — master (#36)
by Jindun
03:06
created

Service   F

Complexity

Total Complexity 85

Size/Duplication

Total Lines 532
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 187
dl 0
loc 532
rs 2
c 0
b 0
f 0
wmc 85

58 Methods

Rating   Name   Duplication   Size   Complexity  
A getPorts() 0 3 1
A getImage() 0 3 1
A getCommand() 0 3 1
A jsonSerialize() 0 46 4
A getEnvironment() 0 3 1
A getServiceName() 0 3 1
A getLabels() 0 3 1
B parsePayload() 0 35 6
A __construct() 0 3 1
A getInternalPorts() 0 3 1
A getVolumes() 0 3 1
A checkValidity() 0 13 3
A getDependsOn() 0 3 1
A imageJsonSerialize() 0 25 6
A addPort() 0 5 1
A getNeedVirtualHost() 0 3 1
A getDockerfileCommands() 0 3 1
A getDestEnvTypes() 0 3 1
A addTmpfsVolume() 0 3 1
A addEnvVar() 0 17 5
A setInternalPorts() 0 3 1
A setNeedBuild() 0 3 1
A addVolume() 0 14 4
A getRequestCpu() 0 3 1
A setNeedVirtualHost() 0 3 1
A addDependsOn() 0 3 1
A addCommand() 0 3 1
A getLimitMemory() 0 3 1
A setImage() 0 3 1
A addBindVolume() 0 3 1
A setRequestMemory() 0 3 1
A setLimitCpu() 0 3 1
A setLimitMemory() 0 3 1
A addNamedVolume() 0 3 1
A setCommand() 0 3 1
A addLabel() 0 4 1
A getRequestMemory() 0 3 1
A getLimitCpu() 0 3 1
A addSharedSecret() 0 3 1
A addSharedEnvVariable() 0 3 1
A getNeedBuild() 0 3 1
A setRequestCpu() 0 3 1
A setServiceName() 0 3 1
A setDependsOn() 0 3 1
A addInternalPort() 0 3 1
A addDockerfileCommand() 0 3 1
A addDestEnvType() 0 6 2
A removeAllNamedVolumes() 0 3 1
A removeAllTmpfsVolumes() 0 3 1
A removeAllBindVolumes() 0 3 1
A removeVolumesByType() 0 6 1
A removeVolumesBySource() 0 6 1
A isForProdEnvType() 0 3 2
A isForDevEnvType() 0 3 2
A isForTestEnvType() 0 3 2
A isForMyEnvType() 0 4 2
A addContainerEnvVariable() 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 JsonSerializable;
6
use Opis\JsonSchema\ValidationError;
7
use Opis\JsonSchema\Validator;
8
use TheAentMachine\Aenthill\Manifest;
9
use TheAentMachine\Aenthill\CommonMetadata;
10
use TheAentMachine\Service\Enum\EnvVariableTypeEnum;
11
use TheAentMachine\Service\Enum\VolumeTypeEnum;
12
use TheAentMachine\Service\Environment\EnvVariable;
13
use TheAentMachine\Service\Exception\ServiceException;
14
use TheAentMachine\Service\Volume\BindVolume;
15
use TheAentMachine\Service\Volume\NamedVolume;
16
use TheAentMachine\Service\Volume\TmpfsVolume;
17
use TheAentMachine\Service\Volume\Volume;
18
19
class Service implements JsonSerializable
20
{
21
    /** @var string */
22
    private $serviceName = '';
23
    /** @var string|null */
24
    private $image;
25
    /** @var string[] */
26
    private $command = [];
27
    /** @var int[] */
28
    private $internalPorts = [];
29
    /** @var string[] */
30
    private $dependsOn = [];
31
    /** @var mixed[] */
32
    private $ports = [];
33
    /** @var mixed[] */
34
    private $labels = [];
35
    /** @var mixed[] */
36
    private $environment = [];
37
    /** @var mixed[] */
38
    private $volumes = [];
39
    /** @var null|bool */
40
    private $needVirtualHost;
41
    /** @var null|bool */
42
    private $needBuild;
43
    /** @var \stdClass */
44
    private $validatorSchema;
45
    /** @var string[] */
46
    private $dockerfileCommands = [];
47
    /** @var string */
48
    private $requestMemory = '';
49
    /** @var string */
50
    private $requestCpu = '';
51
    /** @var string */
52
    private $limitMemory = '';
53
    /** @var string */
54
    private $limitCpu = '';
55
56
    /** @var string[] */
57
    private $destEnvTypes = []; // empty === all env types
58
59
    /**
60
     * Service constructor.
61
     */
62
    public function __construct()
63
    {
64
        $this->validatorSchema = \GuzzleHttp\json_decode((string)file_get_contents(__DIR__ . '/ServiceJsonSchema.json'), false);
65
    }
66
67
    /**
68
     * @param mixed[] $payload
69
     * @return Service
70
     * @throws ServiceException
71
     */
72
    public static function parsePayload(array $payload): Service
73
    {
74
        $service = new self();
75
        $service->checkValidity($payload);
76
        $service->serviceName = $payload['serviceName'] ?? '';
77
        $s = $payload['service'] ?? [];
78
        if (!empty($s)) {
79
            $service->image = $s['image'] ?? null;
80
            $service->command = $s['command'] ?? [];
81
            $service->internalPorts = $s['internalPorts'] ?? [];
82
            $service->dependsOn = $s['dependsOn'] ?? [];
83
            $service->ports = $s['ports'] ?? [];
84
            $service->labels = $s['labels'] ?? [];
85
            if (!empty($s['environment'])) {
86
                foreach ($s['environment'] as $key => $env) {
87
                    $service->addEnvVar($key, $env['value'], $env['type']);
88
                }
89
            }
90
            if (!empty($s['volumes'])) {
91
                foreach ($s['volumes'] as $vol) {
92
                    $service->addVolume($vol['type'], $vol['source'], $vol['target'] ?? '', $vol['readOnly'] ?? false);
93
                }
94
            }
95
            $service->needVirtualHost = $s['needVirtualHost'] ?? null;
96
            $service->needBuild = $s['needBuild'] ?? null;
97
        }
98
        $service->dockerfileCommands = $payload['dockerfileCommands'] ?? [];
99
        $service->destEnvTypes = $payload['destEnvTypes'] ?? [];
100
        $service->dockerfileCommands = $payload['dockerfileCommands'] ?? '';
0 ignored issues
show
Documentation Bug introduced by
It seems like $payload['dockerfileCommands'] ?? '' can also be of type string. However, the property $dockerfileCommands is declared as type string[]. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
101
102
        $service->requestMemory = $payload['requestMemory'] ?? '';
103
        $service->requestCpu = $payload['requestCpu'] ?? '';
104
        $service->limitMemory = $payload['limitMemory'] ?? '';
105
        $service->limitCpu = $payload['limitCpu'] ?? '';
106
        return $service;
107
    }
108
109
    /**
110
     * Specify data which should be serialized to JSON
111
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
112
     * @return array data which can be serialized by <b>json_encode</b>,
113
     * which is a value of any type other than a resource.
114
     * @since 5.4.0
115
     * @throws ServiceException
116
     */
117
    public function jsonSerialize(): array
118
    {
119
        $jsonSerializeMap = function (JsonSerializable $obj): array {
120
            return $obj->jsonSerialize();
121
        };
122
123
        $json = array(
124
            'serviceName' => $this->serviceName,
125
        );
126
127
        $service = array_filter([
128
            'image' => $this->image,
129
            'command' => $this->command,
130
            'internalPorts' => $this->internalPorts,
131
            'dependsOn' => $this->dependsOn,
132
            'ports' => $this->ports,
133
            'labels' => $this->labels,
134
            'environment' => array_map($jsonSerializeMap, $this->environment),
135
            'volumes' => array_map($jsonSerializeMap, $this->volumes),
136
            'needVirtualHost' => $this->needVirtualHost,
137
            'needBuild' => $this->needBuild,
138
        ]);
139
140
        if (!empty($service)) {
141
            $json['service'] = $service;
142
        }
143
144
        if (!empty($this->dockerfileCommands)) {
145
            $json['dockerfileCommands'] = $this->dockerfileCommands;
146
        }
147
148
        $json['destEnvTypes'] = $this->destEnvTypes;
149
150
        $resources = array_filter([
151
            'requestMemory' => $this->requestMemory,
152
            'requestCpu' => $this->requestCpu,
153
            'limitMemory' => $this->limitMemory,
154
            'limitCpu' => $this->limitCpu
155
        ]);
156
157
        if (!empty($resources)) {
158
            $json = array_merge($json, $resources);
159
        }
160
161
        $this->checkValidity($json);
162
        return $json;
163
    }
164
165
    /** @return mixed[] */
166
    public function imageJsonSerialize(): array
167
    {
168
        $dockerfileCommands = [];
169
        $dockerfileCommands[] = 'FROM ' . $this->image;
170
        foreach ($this->environment as $key => $env) {
171
            if ($env->getType() === EnvVariableTypeEnum::IMAGE_ENV_VARIABLE) {
172
                $dockerfileCommands[] = "ENV $key" . '=' . $env->getValue();
173
            }
174
        }
175
        foreach ($this->volumes as $volume) {
176
            if ($volume->getType() === VolumeTypeEnum::BIND_VOLUME) {
177
                $dockerfileCommands[] = 'COPY ' . $volume->getSource() . ' ' . $volume->getTarget();
178
            }
179
        }
180
181
        if (!empty($this->command)) {
182
            $dockerfileCommands[] = 'CMD ' . implode(' ', $this->command);
183
        }
184
185
        $dockerfileCommands = array_merge($dockerfileCommands, $this->dockerfileCommands);
186
187
        return [
188
            'serviceName' => $this->serviceName,
189
            'dockerfileCommands' => $dockerfileCommands,
190
            'destEnvTypes' => $this->destEnvTypes,
191
        ];
192
    }
193
194
    /**
195
     * @param \stdClass|array|string $data
196
     * @return bool
197
     * @throws ServiceException
198
     */
199
    private function checkValidity($data): bool
200
    {
201
        if (\is_array($data)) {
202
            $data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($data), false);
203
        }
204
        $validator = new Validator();
205
        $result = $validator->dataValidation($data, $this->validatorSchema);
206
        if (!$result->isValid()) {
207
            /** @var ValidationError $vError */
208
            $vError = $result->getFirstError();
209
            throw ServiceException::invalidServiceData($vError);
210
        }
211
        return $result->isValid();
212
    }
213
214
215
    /************************ getters **********************/
216
217
    public function getServiceName(): string
218
    {
219
        return $this->serviceName;
220
    }
221
222
    public function getImage(): ?string
223
    {
224
        return $this->image;
225
    }
226
227
    /** @return string[] */
228
    public function getCommand(): array
229
    {
230
        return $this->command;
231
    }
232
233
    /** @return int[] */
234
    public function getInternalPorts(): array
235
    {
236
        return $this->internalPorts;
237
    }
238
239
    /** @return string[] */
240
    public function getDependsOn(): array
241
    {
242
        return $this->dependsOn;
243
    }
244
245
    /** @return mixed[] */
246
    public function getPorts(): array
247
    {
248
        return $this->ports;
249
    }
250
251
    /** @return mixed[] */
252
    public function getLabels(): array
253
    {
254
        return $this->labels;
255
    }
256
257
    /** @return mixed[] */
258
    public function getEnvironment(): array
259
    {
260
        return $this->environment;
261
    }
262
263
    /** @return mixed[] */
264
    public function getVolumes(): array
265
    {
266
        return $this->volumes;
267
    }
268
269
    public function getNeedVirtualHost(): ?bool
270
    {
271
        return $this->needVirtualHost;
272
    }
273
274
    public function getNeedBuild(): ?bool
275
    {
276
        return $this->needBuild;
277
    }
278
279
    /** @return string[] */
280
    public function getDockerfileCommands(): array
281
    {
282
        return $this->dockerfileCommands;
283
    }
284
285
    public function getRequestMemory(): string
286
    {
287
        return $this->requestMemory;
288
    }
289
290
    public function getRequestCpu(): string
291
    {
292
        return $this->requestCpu;
293
    }
294
295
    public function getLimitMemory(): string
296
    {
297
        return $this->limitMemory;
298
    }
299
300
    public function getLimitCpu(): string
301
    {
302
        return $this->limitCpu;
303
    }
304
305
    /**  @return string[] */
306
    public function getDestEnvTypes(): array
307
    {
308
        return $this->destEnvTypes;
309
    }
310
311
312
    /************************ setters **********************/
313
314
    public function setServiceName(string $serviceName): void
315
    {
316
        $this->serviceName = $serviceName;
317
    }
318
319
    public function setImage(?string $image): void
320
    {
321
        $this->image = $image;
322
    }
323
324
    /** @param string[] $command */
325
    public function setCommand(array $command): void
326
    {
327
        $this->command = $command;
328
    }
329
330
    /** @param int[] $internalPorts */
331
    public function setInternalPorts(array $internalPorts): void
332
    {
333
        $this->internalPorts = $internalPorts;
334
    }
335
336
    /** @param string[] $dependsOn */
337
    public function setDependsOn(array $dependsOn): void
338
    {
339
        $this->dependsOn = $dependsOn;
340
    }
341
342
    public function setRequestMemory(string $requestMemory): void
343
    {
344
        $this->requestMemory = $requestMemory;
345
    }
346
347
    public function setRequestCpu(string $requestCpu): void
348
    {
349
        $this->requestCpu = $requestCpu;
350
    }
351
352
    public function setLimitMemory(string $limitMemory): void
353
    {
354
        $this->limitMemory = $limitMemory;
355
    }
356
357
    public function setLimitCpu(string $limitCpu): void
358
    {
359
        $this->limitCpu = $limitCpu;
360
    }
361
362
    public function setNeedVirtualHost(?bool $needVirtualHost): void
363
    {
364
        $this->needVirtualHost = $needVirtualHost;
365
    }
366
367
    public function setNeedBuild(?bool $needBuild): void
368
    {
369
        $this->needBuild = $needBuild;
370
    }
371
372
373
    /************************ adders **********************/
374
375
    public function addCommand(string $command): void
376
    {
377
        $this->command[] = $command;
378
    }
379
380
    public function addInternalPort(int $internalPort): void
381
    {
382
        $this->internalPorts[] = $internalPort;
383
    }
384
385
    public function addDependsOn(string $dependsOn): void
386
    {
387
        $this->dependsOn[] = $dependsOn;
388
    }
389
390
    public function addPort(int $source, int $target): void
391
    {
392
        $this->ports[] = array(
393
            'source' => $source,
394
            'target' => $target,
395
        );
396
    }
397
398
    public function addLabel(string $key, string $value): void
399
    {
400
        $this->labels[$key] = array(
401
            'value' => $value,
402
        );
403
    }
404
405
406
    /************************ environment adders **********************/
407
408
    /** @throws ServiceException */
409
    private function addEnvVar(string $key, string $value, string $type): void
410
    {
411
        switch ($type) {
412
            case EnvVariableTypeEnum::SHARED_ENV_VARIABLE:
413
                $this->addSharedEnvVariable($key, $value);
414
                break;
415
            case EnvVariableTypeEnum::SHARED_SECRET:
416
                $this->addSharedSecret($key, $value);
417
                break;
418
            case EnvVariableTypeEnum::IMAGE_ENV_VARIABLE:
419
                $this->addImageEnvVariable($key, $value);
420
                break;
421
            case EnvVariableTypeEnum::CONTAINER_ENV_VARIABLE:
422
                $this->addContainerEnvVariable($key, $value);
423
                break;
424
            default:
425
                throw ServiceException::unknownEnvVariableType($type);
426
        }
427
    }
428
429
    public function addSharedEnvVariable(string $key, string $value): void
430
    {
431
        $this->environment[$key] = new EnvVariable($value, EnvVariableTypeEnum::SHARED_ENV_VARIABLE);
432
    }
433
434
    public function addSharedSecret(string $key, string $value): void
435
    {
436
        $this->environment[$key] = new EnvVariable($value, EnvVariableTypeEnum::SHARED_SECRET);
437
    }
438
439
    public function addImageEnvVariable(string $key, string $value): void
440
    {
441
        $this->environment[$key] = new EnvVariable($value, EnvVariableTypeEnum::IMAGE_ENV_VARIABLE);
442
    }
443
444
    public function addContainerEnvVariable(string $key, string $value): void
445
    {
446
        $this->environment[$key] = new EnvVariable($value, EnvVariableTypeEnum::CONTAINER_ENV_VARIABLE);
447
    }
448
449
450
    /************************ volumes adders & removers **********************/
451
452
    /** @throws ServiceException */
453
    private function addVolume(string $type, string $source, string $target = '', bool $readOnly = false): void
454
    {
455
        switch ($type) {
456
            case VolumeTypeEnum::NAMED_VOLUME:
457
                $this->addNamedVolume($source, $target, $readOnly);
458
                break;
459
            case VolumeTypeEnum::BIND_VOLUME:
460
                $this->addBindVolume($source, $target, $readOnly);
461
                break;
462
            case VolumeTypeEnum::TMPFS_VOLUME:
463
                $this->addTmpfsVolume($source);
464
                break;
465
            default:
466
                throw ServiceException::unknownVolumeType($type);
467
        }
468
    }
469
470
    public function addNamedVolume(string $source, string $target, bool $readOnly = false): void
471
    {
472
        $this->volumes[] = new NamedVolume($source, $target, $readOnly);
473
    }
474
475
    public function addBindVolume(string $source, string $target, bool $readOnly = false): void
476
    {
477
        $this->volumes[] = new BindVolume($source, $target, $readOnly);
478
    }
479
480
    public function addTmpfsVolume(string $source): void
481
    {
482
        $this->volumes[] = new TmpfsVolume($source);
483
    }
484
485
    public function addDockerfileCommand(string $dockerfileCommand): void
486
    {
487
        $this->dockerfileCommands[] = $dockerfileCommand;
488
    }
489
490
    private function removeVolumesByType(string $type): void
491
    {
492
        $filterFunction = function (Volume $vol) use ($type) {
493
            return $vol->getType() !== $type;
494
        };
495
        $this->volumes = array_values(array_filter($this->volumes, $filterFunction));
496
    }
497
498
    public function removeAllBindVolumes(): void
499
    {
500
        $this->removeVolumesByType(VolumeTypeEnum::BIND_VOLUME);
501
    }
502
503
    public function removeAllNamedVolumes(): void
504
    {
505
        $this->removeVolumesByType(VolumeTypeEnum::NAMED_VOLUME);
506
    }
507
508
    public function removeAllTmpfsVolumes(): void
509
    {
510
        $this->removeVolumesByType(VolumeTypeEnum::TMPFS_VOLUME);
511
    }
512
513
    public function removeVolumesBySource(string $source): void
514
    {
515
        $filterFunction = function (Volume $vol) use ($source) {
516
            return $vol->getSource() !== $source;
517
        };
518
        $this->volumes = array_values(array_filter($this->volumes, $filterFunction));
519
    }
520
521
522
    /************************ destEnvTypes stuffs **********************/
523
524
    public function addDestEnvType(string $envType, bool $keepTheOtherEnvTypes = true): void
525
    {
526
        if (!$keepTheOtherEnvTypes) {
527
            $this->destEnvTypes = [];
528
        }
529
        $this->destEnvTypes[] = $envType;
530
    }
531
532
    public function isForDevEnvType(): bool
533
    {
534
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_DEV, $this->destEnvTypes);
535
    }
536
537
    public function isForTestEnvType(): bool
538
    {
539
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_TEST, $this->destEnvTypes);
540
    }
541
542
    public function isForProdEnvType(): bool
543
    {
544
        return empty($this->destEnvTypes) || \in_array(CommonMetadata::ENV_TYPE_PROD, $this->destEnvTypes);
545
    }
546
547
    public function isForMyEnvType(): bool
548
    {
549
        $myEnvType = Manifest::getMetadata(CommonMetadata::ENV_TYPE_KEY);
550
        return empty($this->destEnvTypes) || \in_array($myEnvType, $this->destEnvTypes, true);
551
    }
552
}
553