Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.02% covered (success)
93.02%
40 / 43
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
UpdateManifestDto
92.86% covered (success)
92.86%
39 / 42
40.00% covered (danger)
40.00%
2 / 5
30.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromArray
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 parseFilesAddOrUpdate
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
8.05
 parseFilesDelete
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
6.10
 parseRequiredExtensions
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
6.10
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Updater\Dto;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Data Transfer Object representing an update package manifest.
13 *
14 * @package App\Core\Updater\Dto
15 */
16final readonly class UpdateManifestDto
17{
18    /**
19     * UpdateManifestDto constructor.
20     *
21     * @param string $version Target release version (e.g. 0.0.2).
22     * @param string|null $fromVersion Minimum required current version.
23     * @param string $name Package identifier name.
24     * @param string|null $releaseDate Release ISO date.
25     * @param string $phpMin Minimum required PHP version.
26     * @param list<string> $requiredExtensions Required PHP extension list.
27     * @param list<array{target_path: string, sha256?: string, permissions?: string}> $filesAddOrUpdate
28     * @param list<string> $filesDelete List of relative file paths to delete.
29     * @param string|null $sqlUp Relative path to SQL upgrade script.
30     * @param string|null $sqlDown Relative path to SQL rollback script.
31     * @param string|null $signature Cryptographic signature or hash.
32     */
33    public function __construct(
34        public string $version,
35        public ?string $fromVersion = null,
36        public string $name = 'ammonly/update-package',
37        public ?string $releaseDate = null,
38        public string $phpMin = '8.2.0',
39        public array $requiredExtensions = [],
40        public array $filesAddOrUpdate = [],
41        public array $filesDelete = [],
42        public ?string $sqlUp = null,
43        public ?string $sqlDown = null,
44        public ?string $signature = null
45    ) {
46    }
47
48    /**
49     * Creates DTO from associative array parsed from manifest.json.
50     *
51     * @param array<string, mixed> $data
52     */
53    public static function fromArray(array $data): self
54    {
55        $requirements = is_array($data['requirements'] ?? null) ? $data['requirements'] : [];
56        $files = is_array($data['files'] ?? null) ? $data['files'] : [];
57        $database = is_array($data['database'] ?? null) ? $data['database'] : [];
58
59        return new self(
60            version: (string) ($data['version'] ?? '0.0.1'),
61            fromVersion: isset($data['from_version']) ? (string) $data['from_version'] : null,
62            name: (string) ($data['name'] ?? 'ammonly/update-package'),
63            releaseDate: isset($data['release_date']) ? (string) $data['release_date'] : null,
64            phpMin: (string) ($requirements['php_min'] ?? '8.2.0'),
65            requiredExtensions: self::parseRequiredExtensions($requirements),
66            filesAddOrUpdate: self::parseFilesAddOrUpdate($files),
67            filesDelete: self::parseFilesDelete($files),
68            sqlUp: isset($database['up_script']) ? (string) $database['up_script'] : null,
69            sqlDown: isset($database['down_script']) ? (string) $database['down_script'] : null,
70            signature: isset($data['signature']) ? (string) $data['signature'] : null
71        );
72    }
73
74    /**
75     * @param array<string, mixed> $files
76     * @return list<array{target_path: string, sha256: string|null, permissions: string}>
77     */
78    private static function parseFilesAddOrUpdate(array $files): array
79    {
80        $addOrUpdate = [];
81        if (!isset($files['add_or_update']) || !is_array($files['add_or_update'])) {
82            return $addOrUpdate;
83        }
84
85        foreach ($files['add_or_update'] as $item) {
86            if (is_array($item) && isset($item['target_path'])) {
87                $addOrUpdate[] = [
88                    'target_path' => (string) $item['target_path'],
89                    'sha256' => isset($item['sha256']) ? (string) $item['sha256'] : null,
90                    'permissions' => isset($item['permissions']) ? (string) $item['permissions'] : '0644',
91                ];
92            }
93        }
94
95        return $addOrUpdate;
96    }
97
98    /**
99     * @param array<string, mixed> $files
100     * @return list<string>
101     */
102    private static function parseFilesDelete(array $files): array
103    {
104        $delete = [];
105        if (!isset($files['delete']) || !is_array($files['delete'])) {
106            return $delete;
107        }
108
109        foreach ($files['delete'] as $delItem) {
110            if (is_string($delItem) && trim($delItem) !== '') {
111                $delete[] = trim($delItem);
112            }
113        }
114
115        return $delete;
116    }
117
118    /**
119     * @param array<string, mixed> $requirements
120     * @return list<string>
121     */
122    private static function parseRequiredExtensions(array $requirements): array
123    {
124        $extensions = [];
125        if (!isset($requirements['extensions']) || !is_array($requirements['extensions'])) {
126            return $extensions;
127        }
128
129        foreach ($requirements['extensions'] as $ext) {
130            if (is_string($ext) && trim($ext) !== '') {
131                $extensions[] = trim($ext);
132            }
133        }
134
135        return $extensions;
136    }
137}