Total Complexity | 48 |
Total Lines | 273 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like Version20250926174000 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 Version20250926174000, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | final class Version20250926174000 extends AbstractMigrationChamilo |
||
19 | { |
||
20 | public function getDescription(): string |
||
21 | { |
||
22 | return 'Upsert settings (title/comment/category/value) + settings_value_template.'; |
||
23 | } |
||
24 | |||
25 | public function up(Schema $schema): void |
||
26 | { |
||
27 | // Collect settings (existing + new) from fixtures |
||
28 | $rows = []; |
||
29 | $this->collectFromFixtures($rows, (array) SettingsCurrentFixtures::getExistingSettings()); |
||
30 | $this->collectFromFixtures($rows, (array) SettingsCurrentFixtures::getNewConfigurationSettings()); |
||
31 | |||
32 | // Index by variable (last occurrence wins) |
||
33 | $byVariable = []; |
||
34 | foreach ($rows as $r) { |
||
35 | $byVariable[$r['variable']] = $r; |
||
36 | } |
||
37 | |||
38 | // Defaults from SettingsManager schemas |
||
39 | $defaults = $this->buildDefaultsFromSchemas(); |
||
40 | |||
41 | foreach ($byVariable as $variable => $row) { |
||
42 | if (!$this->isValidIdentifier($variable)) { |
||
43 | error_log(sprintf('[SKIP] Invalid variable name: "%s".', $variable)); |
||
44 | continue; |
||
45 | } |
||
46 | $category = $row['category']; |
||
47 | if (!$this->isValidIdentifier($category)) { |
||
48 | error_log(sprintf('[SKIP] Invalid category for "%s": "%s".', $variable, $category)); |
||
49 | continue; |
||
50 | } |
||
51 | |||
52 | $title = (string) $row['title']; |
||
53 | $comment = (string) $row['comment']; |
||
54 | |||
55 | try { |
||
56 | $exists = (int) $this->connection->fetchOne( |
||
57 | 'SELECT COUNT(*) FROM settings WHERE variable = ?', |
||
58 | [$variable] |
||
59 | ); |
||
60 | } catch (\Throwable $e) { |
||
61 | error_log(sprintf('[ERROR] Existence check failed for "%s": %s', $variable, $e->getMessage())); |
||
62 | continue; |
||
63 | } |
||
64 | |||
65 | if ($exists > 0) { |
||
66 | $sql = 'UPDATE settings SET category = ?, title = ?, comment = ? WHERE variable = ?'; |
||
67 | $params = [$category, $title, $comment, $variable]; |
||
68 | $this->addSql($sql, $params); |
||
69 | } else { |
||
70 | $defaultValue = $defaults[$variable] ?? ''; |
||
71 | $sql = 'INSERT INTO settings (variable, category, title, comment, selected_value, access_url_changeable) VALUES (?, ?, ?, ?, ?, 0)'; |
||
72 | $params = [$variable, $category, $title, $comment, (string) $defaultValue]; |
||
73 | $this->addSql($sql, $params); |
||
74 | } |
||
75 | } |
||
76 | |||
77 | $this->dryRunTemplatesUpsertAndLink(); |
||
78 | } |
||
79 | |||
80 | public function down(Schema $schema): void |
||
81 | { |
||
82 | error_log('[MIGRATION] Down is a no-op (dry-run migration).'); |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * Read fixtures and normalize to: [variable, category, title, comment] |
||
87 | */ |
||
88 | private function collectFromFixtures(array &$out, array $byCategory): void |
||
89 | { |
||
90 | foreach ($byCategory as $categoryKey => $settings) { |
||
91 | $category = strtolower((string) $categoryKey); |
||
92 | |||
93 | foreach ((array) $settings as $setting) { |
||
94 | $variable = (string) ($setting['name'] ?? $setting['variable'] ?? ''); |
||
95 | if ($variable === '') { |
||
96 | error_log(sprintf('[WARN] Missing "name" in fixture entry for category "%s" - skipping.', $category)); |
||
97 | continue; |
||
98 | } |
||
99 | |||
100 | $title = (string) ($setting['title'] ?? $variable); |
||
101 | $comment = (string) ($setting['comment'] ?? ''); |
||
102 | |||
103 | $out[] = [ |
||
104 | 'variable' => $variable, |
||
105 | 'category' => $category, |
||
106 | 'title' => $title, |
||
107 | 'comment' => $comment, |
||
108 | ]; |
||
109 | } |
||
110 | } |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Build default values map by scanning SettingsManager schemas. |
||
115 | * Returns: [ variable => defaultValueAsString, ... ] |
||
116 | */ |
||
117 | private function buildDefaultsFromSchemas(): array |
||
118 | { |
||
119 | $map = []; |
||
120 | |||
121 | $manager = null; |
||
122 | try { |
||
123 | if ($this->container->has('chamilo.settings_manager')) { |
||
|
|||
124 | $manager = $this->container->get('chamilo.settings_manager'); |
||
125 | } elseif ($this->container->has('chamilo.core.settings_manager')) { |
||
126 | $manager = $this->container->get('chamilo.core.settings_manager'); |
||
127 | } elseif ($this->container->has(SettingsManager::class)) { |
||
128 | $manager = $this->container->get(SettingsManager::class); |
||
129 | } |
||
130 | } catch (\Throwable $e) { |
||
131 | error_log('[WARN] Unable to retrieve SettingsManager from container: ' . $e->getMessage()); |
||
132 | } |
||
133 | |||
134 | if (!$manager) { |
||
135 | error_log('[WARN] SettingsManager not found; defaults map will be empty.'); |
||
136 | return $map; |
||
137 | } |
||
138 | |||
139 | try { |
||
140 | $schemas = $manager->getSchemas(); |
||
141 | } catch (\Throwable $e) { |
||
142 | error_log('[WARN] getSchemas() failed on SettingsManager: ' . $e->getMessage()); |
||
143 | return $map; |
||
144 | } |
||
145 | |||
146 | foreach (array_keys($schemas) as $serviceIdOrAlias) { |
||
147 | $namespace = str_replace('chamilo_core.settings.', '', (string) $serviceIdOrAlias); |
||
148 | |||
149 | try { |
||
150 | $settingsBag = $manager->load($namespace); |
||
151 | } catch (\Throwable $e) { |
||
152 | error_log(sprintf('[WARN] load("%s") failed: %s', $namespace, $e->getMessage())); |
||
153 | continue; |
||
154 | } |
||
155 | |||
156 | $parameters = []; |
||
157 | |||
158 | try { |
||
159 | if (method_exists($settingsBag, 'getParameters')) { |
||
160 | $parameters = (array) $settingsBag->getParameters(); |
||
161 | } elseif (method_exists($settingsBag, 'all')) { |
||
162 | $parameters = (array) $settingsBag->all(); |
||
163 | } elseif (method_exists($settingsBag, 'toArray')) { |
||
164 | $parameters = (array) $settingsBag->toArray(); |
||
165 | } |
||
166 | } catch (\Throwable $e) { |
||
167 | error_log(sprintf('[WARN] Could not extract parameters for "%s": %s', $namespace, $e->getMessage())); |
||
168 | $parameters = []; |
||
169 | } |
||
170 | |||
171 | if (empty($parameters)) { |
||
172 | try { |
||
173 | $keys = []; |
||
174 | if (method_exists($settingsBag, 'keys')) { |
||
175 | $keys = (array) $settingsBag->keys(); |
||
176 | } elseif (method_exists($settingsBag, 'getIterator')) { |
||
177 | $keys = array_keys(iterator_to_array($settingsBag->getIterator())); |
||
178 | } |
||
179 | foreach ($keys as $k) { |
||
180 | try { |
||
181 | $parameters[$k] = $settingsBag->get($k); |
||
182 | } catch (\Throwable $e) { |
||
183 | // ignore |
||
184 | } |
||
185 | } |
||
186 | } catch (\Throwable $e) { |
||
187 | error_log(sprintf('[WARN] Parameter keys iteration failed for "%s": %s', $namespace, $e->getMessage())); |
||
188 | } |
||
189 | } |
||
190 | |||
191 | foreach ($parameters as $var => $val) { |
||
192 | $var = (string) $var; |
||
193 | if ($var === '') { |
||
194 | continue; |
||
195 | } |
||
196 | if (!array_key_exists($var, $map)) { |
||
197 | $map[$var] = is_scalar($val) ? (string) $val : (string) json_encode($val); |
||
198 | } |
||
199 | } |
||
200 | } |
||
201 | |||
202 | return $map; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * Upsert settings_value_template and preview linking to settings.value_template_id. |
||
207 | * For safety, we: |
||
208 | * - validate variable |
||
209 | * - JSON-encode examples with proper flags |
||
210 | * - do existence checks with SELECTs |
||
211 | */ |
||
212 | private function dryRunTemplatesUpsertAndLink(): void |
||
213 | { |
||
214 | $grouped = []; |
||
215 | try { |
||
216 | $grouped = (array) SettingsValueTemplateFixtures::getTemplatesGrouped(); |
||
217 | } catch (\Throwable $e) { |
||
218 | error_log('[WARN] Unable to load SettingsValueTemplateFixtures::getTemplatesGrouped(): ' . $e->getMessage()); |
||
219 | return; |
||
220 | } |
||
221 | |||
222 | foreach ($grouped as $category => $items) { |
||
223 | foreach ((array) $items as $setting) { |
||
224 | $variable = (string) ($setting['variable'] ?? $setting['name'] ?? ''); |
||
225 | $jsonExample = $setting['json_example'] ?? null; |
||
226 | |||
227 | if ($variable === '' || !$this->isValidIdentifier($variable)) { |
||
228 | error_log(sprintf('[SKIP] Invalid or empty template variable in category "%s".', (string) $category)); |
||
229 | continue; |
||
230 | } |
||
231 | |||
232 | // Serialize JSON example safely (string for SQL param preview) |
||
233 | try { |
||
234 | $jsonEncoded = json_encode($jsonExample, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
||
235 | } catch (\Throwable $e) { |
||
236 | error_log(sprintf('[WARN] JSON encoding failed for variable "%s": %s', $variable, $e->getMessage())); |
||
237 | $jsonEncoded = 'null'; |
||
238 | } |
||
239 | |||
240 | // Check if template exists |
||
241 | try { |
||
242 | $templateId = $this->connection->fetchOne( |
||
243 | 'SELECT id FROM settings_value_template WHERE variable = ?', |
||
244 | [$variable] |
||
245 | ); |
||
246 | } catch (\Throwable $e) { |
||
247 | error_log(sprintf('[ERROR] Failed to check template existence for "%s": %s', $variable, $e->getMessage())); |
||
248 | continue; |
||
249 | } |
||
250 | |||
251 | if ($templateId) { |
||
252 | // UPDATE PREVIEW on existing template |
||
253 | $sql = 'UPDATE settings_value_template SET json_example = ?, updated_at = NOW() WHERE id = ?'; |
||
254 | $params = [$jsonEncoded, $templateId]; |
||
255 | $this->addSql($sql, $params); |
||
256 | } else { |
||
257 | // INSERT PREVIEW new template |
||
258 | $sql = 'INSERT INTO settings_value_template (variable, json_example, created_at, updated_at) VALUES (?, ?, NOW(), NOW())'; |
||
259 | $params = [$variable, $jsonEncoded]; |
||
260 | $this->addSql($sql, $params); |
||
261 | |||
262 | // Try to discover what ID it would be (optional best-effort) |
||
263 | try { |
||
264 | // We DO NOT insert, so we cannot call lastInsertId(). |
||
265 | // Instead, try to SELECT id if it exists already after a previous run; otherwise log NULL. |
||
266 | $templateId = $this->connection->fetchOne( |
||
267 | 'SELECT id FROM settings_value_template WHERE variable = ?', |
||
268 | [$variable] |
||
269 | ); |
||
270 | } catch (\Throwable $e) { |
||
271 | $templateId = false; |
||
272 | } |
||
273 | } |
||
274 | |||
275 | // Link PREVIEW: settings.value_template_id = $templateId |
||
276 | if ($templateId) { |
||
277 | $sql = 'UPDATE settings SET value_template_id = ? WHERE variable = ?'; |
||
278 | $params = [$templateId, $variable]; |
||
279 | $this->addSql($sql, $params); |
||
280 | } else { |
||
281 | error_log(sprintf('[INFO] Skipping link preview for "%s" (no template id available in dry-run).', $variable)); |
||
282 | } |
||
283 | } |
||
284 | } |
||
285 | } |
||
286 | |||
287 | /** Allow letters, numbers, underscore, dash and dot. */ |
||
288 | private function isValidIdentifier(string $s): bool |
||
291 | } |
||
292 | } |
||
293 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.