Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.31% covered (warning)
67.31%
35 / 52
85.00% covered (success)
85.00%
17 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
OperationRecorder
67.31% covered (warning)
67.31%
35 / 52
85.00% covered (success)
85.00%
17 / 20
41.48
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
 addSql
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addSqlForDbType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addConfig
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteConfig
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renameConfig
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 updateConfig
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 copyFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 copyDirectory
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 grantPermission
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createUser
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 insertFormInput
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addOperation
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getOperations
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOperationsByType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSqlQueries
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getOperationCounts
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Collects all operations from a migration for review or execution.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2023-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-01-25
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Setup\Migration\Operations;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Database;
24use phpMyFAQ\Filesystem\Filesystem;
25
26class OperationRecorder
27{
28    /** @var OperationInterface[] */
29    private array $operations = [];
30
31    public function __construct(
32        private readonly Configuration $configuration,
33        private readonly ?Filesystem $filesystem = null,
34    ) {
35    }
36
37    /**
38     * Records a SQL query operation.
39     */
40    public function addSql(string $query, string $description = ''): self
41    {
42        $this->operations[] = new SqlOperation($this->configuration, $query, $description);
43        return $this;
44    }
45
46    /**
47     * Records a SQL query operation with database type condition.
48     *
49     * @param string|string[] $dbTypes One or more database types (mysqli, pgsql, sqlite3, sqlsrv)
50     */
51    public function addSqlForDbType(string $query, string|array $dbTypes, string $description = ''): self
52    {
53        $dbTypes = (array) $dbTypes;
54        if (in_array(Database::getType(), $dbTypes, strict: true)) {
55            $this->operations[] = new SqlOperation($this->configuration, $query, $description);
56        }
57        return $this;
58    }
59
60    /**
61     * Records a configuration add operation.
62     */
63    public function addConfig(string $key, mixed $value): self
64    {
65        $this->operations[] = new ConfigAddOperation($this->configuration, $key, $value);
66        return $this;
67    }
68
69    /**
70     * Records a configuration delete operation.
71     */
72    public function deleteConfig(string $key): self
73    {
74        $this->operations[] = new ConfigDeleteOperation($this->configuration, $key);
75        return $this;
76    }
77
78    /**
79     * Records a configuration rename operation.
80     */
81    public function renameConfig(string $oldKey, string $newKey): self
82    {
83        $this->operations[] = new ConfigRenameOperation($this->configuration, $oldKey, $newKey);
84        return $this;
85    }
86
87    /**
88     * Records a configuration update operation.
89     */
90    public function updateConfig(string $key, mixed $value): self
91    {
92        $this->operations[] = new ConfigUpdateOperation($this->configuration, $key, $value);
93        return $this;
94    }
95
96    /**
97     * Records a file copy operation.
98     */
99    public function copyFile(string $source, string $destination, bool $onlyIfExists = true): self
100    {
101        $filesystem = $this->filesystem ?? new Filesystem((string) PMF_ROOT_DIR);
102        $this->operations[] = new FileCopyOperation($filesystem, $source, $destination, $onlyIfExists);
103        return $this;
104    }
105
106    /**
107     * Records a directory copy operation.
108     */
109    public function copyDirectory(string $source, string $destination, bool $onlyIfExists = true): self
110    {
111        $filesystem = $this->filesystem ?? new Filesystem((string) PMF_ROOT_DIR);
112        $this->operations[] = new DirectoryCopyOperation($filesystem, $source, $destination, $onlyIfExists);
113        return $this;
114    }
115
116    /**
117     * Records a permission grant operation.
118     */
119    public function grantPermission(string $name, string $description, int $userId = 1): self
120    {
121        $this->operations[] = new PermissionGrantOperation($this->configuration, $name, $description, $userId);
122        return $this;
123    }
124
125    /**
126     * Records a user creation operation.
127     */
128    public function createUser(
129        string $loginName,
130        #[\SensitiveParameter]
131        string $password,
132        string $displayName,
133        string $email,
134        int $userId,
135        bool $isSuperAdmin = false,
136        string $status = 'protected',
137    ): self {
138        $this->operations[] = new UserCreateOperation(
139            $this->configuration,
140            $loginName,
141            $password,
142            $displayName,
143            $email,
144            $userId,
145            $isSuperAdmin,
146            $status,
147        );
148        return $this;
149    }
150
151    /**
152     * Records a form input insertion operation.
153     *
154     * @param array<string, int|string> $formInput
155     */
156    public function insertFormInput(array $formInput): self
157    {
158        $this->operations[] = new FormInputInsertOperation($this->configuration, $formInput);
159        return $this;
160    }
161
162    /**
163     * Records a custom operation.
164     */
165    public function addOperation(OperationInterface $operation): self
166    {
167        $this->operations[] = $operation;
168        return $this;
169    }
170
171    /**
172     * Returns all recorded operations.
173     *
174     * @return OperationInterface[]
175     */
176    public function getOperations(): array
177    {
178        return $this->operations;
179    }
180
181    /**
182     * Returns operations filtered by type.
183     *
184     * @return OperationInterface[]
185     */
186    public function getOperationsByType(string $type): array
187    {
188        return array_filter($this->operations, static fn(OperationInterface $op) => $op->getType() === $type);
189    }
190
191    /**
192     * Returns all SQL queries.
193     *
194     * @return string[]
195     */
196    public function getSqlQueries(): array
197    {
198        return array_map(static fn(OperationInterface $op) => $op instanceof SqlOperation
199            ? $op->getQuery()
200            : '', $this->getOperationsByType('sql'));
201    }
202
203    /**
204     * Returns the count of operations by type.
205     *
206     * @return array<string, int>
207     */
208    public function getOperationCounts(): array
209    {
210        $counts = [];
211        foreach ($this->operations as $operation) {
212            $type = $operation->getType();
213            $counts[$type] = ($counts[$type] ?? 0) + 1;
214        }
215        return $counts;
216    }
217
218    /**
219     * Returns all operations as an array for dry-run output.
220     *
221     * @return array<int, array<string, mixed>>
222     */
223    public function toArray(): array
224    {
225        return array_map(static fn(OperationInterface $op): array => $op->toArray(), array_values($this->operations));
226    }
227
228    /**
229     * Clears all recorded operations.
230     */
231    public function clear(): self
232    {
233        $this->operations = [];
234        return $this;
235    }
236
237    /**
238     * Returns the total count of operations.
239     */
240    public function count(): int
241    {
242        return count($this->operations);
243    }
244}