Passed
Pull Request — master (#36)
by Jindun
02:06
created

Service::addSharedEnvVariable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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