Total Complexity | 56 |
Total Lines | 318 |
Duplicated Lines | 0 % |
Changes | 18 | ||
Bugs | 5 | Features | 0 |
Complex classes like CheckClassNames 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 CheckClassNames, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class CheckClassNames extends MigrateDataTaskBase |
||
17 | { |
||
18 | protected $title = 'Check all tables for valid class names'; |
||
19 | |||
20 | protected $description = 'Check all tables for valid class names'; |
||
21 | |||
22 | private static $segment = 'check-class-names'; |
||
23 | |||
24 | protected $enabled = true; |
||
25 | |||
26 | protected $listOfAllClasses = []; |
||
27 | |||
28 | protected $countsOfAllClasses = []; |
||
29 | |||
30 | protected $dbTablesPresent = []; |
||
31 | |||
32 | protected $fixErrors = true; |
||
33 | |||
34 | protected $extendFieldSize = true; |
||
35 | |||
36 | protected $forReal = true; |
||
37 | |||
38 | protected $dataObjectSchema; |
||
39 | |||
40 | protected $onlyRunFor = []; |
||
41 | |||
42 | protected $bestClassNameStore = []; |
||
43 | |||
44 | /** |
||
45 | * example: |
||
46 | * [ |
||
47 | * ClassName => [ |
||
48 | * FieldA, |
||
49 | * FieldB, |
||
50 | * ]. |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | private static $other_fields_to_check = [ |
||
55 | '\\DNADesign\\\Elemental\\Models\\ElementalArea' => [ |
||
56 | 'OwnerClassName', |
||
57 | ], |
||
58 | SiteTreeLink::class => [ |
||
59 | 'ParentClass', |
||
60 | ], |
||
61 | ]; |
||
62 | |||
63 | protected function performMigration() |
||
134 | } |
||
135 | } |
||
136 | } |
||
137 | |||
138 | protected function fixClassNames(string $tableName, string $objectClassName, ?string $fieldName = 'ClassName', ?bool $versionedTable = false) |
||
139 | { |
||
140 | $this->flushNow('... CHECKING ' . $tableName . '.' . $fieldName . ' ...'); |
||
141 | $count = DB::query('SELECT COUNT("ID") FROM "' . $tableName . '"')->value(); |
||
142 | $where = '"' . $fieldName . '" NOT IN (\'' . implode("', '", array_keys($this->listOfAllClasses)) . "')"; |
||
143 | $whereA = $where . ' AND ' . '(' . '"' . $fieldName . '" IS NULL OR "' . $fieldName . '" = \'\' )'; |
||
144 | $whereB = $where . ' AND NOT ' . '(' . '"' . $fieldName . '" IS NULL OR "' . $fieldName . '" = \'\' )'; |
||
145 | $rowsToFix = DB::query('SELECT COUNT("ID") FROM "' . $tableName . '" WHERE ' . $where)->value(); |
||
146 | $rowsToFixA = DB::query('SELECT COUNT("ID") FROM "' . $tableName . '" WHERE ' . $whereA)->value(); |
||
147 | $rowsToFixB = DB::query('SELECT COUNT("ID") FROM "' . $tableName . '" WHERE ' . $whereB)->value(); |
||
148 | if ($rowsToFix > 0) { |
||
149 | if ($count === $rowsToFix) { |
||
150 | $this->flushNow('... All rows ' . $count . ' in table ' . $tableName . ' are broken: ', 'error'); |
||
151 | } else { |
||
152 | $this->flushNow('... ' . $rowsToFix . ' errors in "' . $fieldName . '" values:'); |
||
153 | if ($rowsToFixA) { |
||
154 | $this->flushNow('... ... ' . $rowsToFixA . ' in table ' . $tableName . ' do not have a ' . $fieldName . ' at all and ', 'error'); |
||
155 | } |
||
156 | if ($rowsToFixB) { |
||
157 | $this->flushNow('... ... ' . $rowsToFixB . ' in table ' . $tableName . ' have a bad ' . $fieldName . ''); |
||
158 | } |
||
159 | } |
||
160 | if ($this->fixErrors) { |
||
161 | if ($this->extendFieldSize) { |
||
162 | $this->fixFieldSize($tableName); |
||
163 | } |
||
164 | //work out if we can set it to the long form of a short ClassName |
||
165 | $rows = DB::query( |
||
166 | ' |
||
167 | SELECT "' . $fieldName . '", COUNT("ID") AS C |
||
168 | FROM "' . $tableName . '" |
||
169 | GROUP BY "' . $fieldName . '" |
||
170 | HAVING ' . $where . ' |
||
171 | ORDER BY C DESC' |
||
172 | ); |
||
173 | foreach ($rows as $row) { |
||
174 | if (! $row[$fieldName]) { |
||
175 | $row[$fieldName] = '--- NO VALUE ---'; |
||
176 | } |
||
177 | $this->flushNow('... ... ' . $row['C'] . ' ' . $row[$fieldName]); |
||
178 | if (isset($this->countsOfAllClasses[$row[$fieldName]])) { |
||
179 | if (1 === $this->countsOfAllClasses[$row[$fieldName]]) { |
||
180 | $longNameAlreadySlashed = array_search($row[$fieldName], $this->listOfAllClasses, true); |
||
181 | if ($longNameAlreadySlashed) { |
||
182 | $this->flushNow('... ... ... Updating ' . $row[$fieldName] . ' to ' . $longNameAlreadySlashed . ' - based in short to long mapping of the ' . $fieldName . ' field. ', 'created'); |
||
183 | if ($this->forReal) { |
||
184 | $this->runUpdateQuery( |
||
185 | ' |
||
186 | UPDATE "' . $tableName . '" |
||
187 | SET "' . $tableName . '"."' . $fieldName . '" = \'' . $longNameAlreadySlashed . '\' |
||
188 | WHERE "' . $fieldName . '" = \'' . $row[$fieldName] . "'", |
||
189 | 2 |
||
190 | ); |
||
191 | } |
||
192 | } |
||
193 | } |
||
194 | } |
||
195 | } |
||
196 | |||
197 | //only try to work out what is going on when it is a ClassName Field! |
||
198 | if ('ClassName' === $fieldName) { |
||
199 | $options = ClassInfo::subclassesFor($objectClassName); |
||
200 | $checkTables = []; |
||
201 | foreach ($options as $key => $optionClassName) { |
||
202 | if ($optionClassName !== $objectClassName) { |
||
203 | $optionTableName = $this->dataObjectSchema->tableName($objectClassName); |
||
204 | if (! $this->tableExists($optionTableName) || $optionTableName === $tableName) { |
||
205 | unset($options[$key]); |
||
206 | } else { |
||
207 | $checkTables[$optionClassName] = $optionTableName; |
||
208 | } |
||
209 | } |
||
210 | } |
||
211 | //fix bad rows.... |
||
212 | $rows = DB::query('SELECT "ID", "' . $fieldName . '" FROM "' . $tableName . '" WHERE ' . $where); |
||
213 | foreach ($rows as $row) { |
||
214 | //check if it is the short name ... |
||
215 | $optionCount = 0; |
||
216 | $matchedClassName = ''; |
||
217 | foreach ($checkTables as $optionClassName => $optionTableName) { |
||
218 | $hasMatch = DB::query(' |
||
219 | SELECT COUNT("' . $tableName . '"."ID") |
||
220 | FROM "' . $tableName . '" |
||
221 | INNER JOIN "' . $optionTableName . '" |
||
222 | ON "' . $optionTableName . '"."ID" = "' . $tableName . '"."ID" |
||
223 | WHERE "' . $tableName . '"."ID" = ' . $row['ID'])->value(); |
||
224 | if (1 === $hasMatch) { |
||
225 | ++$optionCount; |
||
226 | $matchedClassName = $optionClassName; |
||
227 | if ($optionCount > 1) { |
||
228 | break; |
||
229 | } |
||
230 | } |
||
231 | } |
||
232 | if (0 === $optionCount) { |
||
233 | if (! $row[$fieldName]) { |
||
234 | $row[$fieldName] = '--- NO VALUE ---'; |
||
235 | } |
||
236 | $this->flushNow('... Updating ' . $fieldName . ' to ' . $objectClassName . ' for ID = ' . $row['ID'] . ' from ' . $fieldName . ' = ' . $row[$fieldName] . ' - based on inability to find matching IDs in any child class tables', 'created'); |
||
237 | if ($this->forReal) { |
||
238 | $this->runUpdateQuery( |
||
239 | ' |
||
240 | UPDATE "' . $tableName . '" |
||
241 | SET "' . $tableName . '"."' . $fieldName . '" = \'' . addslashes($objectClassName) . '\' |
||
242 | WHERE ID = ' . $row['ID'], |
||
243 | 2 |
||
244 | ); |
||
245 | } |
||
246 | } elseif (1 === $optionCount && $matchedClassName) { |
||
247 | $this->flushNow('... Updating ' . $fieldName . ' to ' . $matchedClassName . ' ID = ' . $row['ID'] . ', ' . $fieldName . ' = ' . $row[$fieldName] . ' - based on matching row in exactly one child class table', 'created'); |
||
248 | if ($this->forReal) { |
||
249 | $this->runUpdateQuery( |
||
250 | 'UPDATE "' . $tableName . '" |
||
251 | SET "' . $tableName . '"."' . $fieldName . '" = \'' . addslashes($matchedClassName) . '\' |
||
252 | WHERE ID = ' . $row['ID'], |
||
253 | 2 |
||
254 | ); |
||
255 | } |
||
256 | } else { |
||
257 | $bestValue = $this->bestClassName($objectClassName, $tableName, $fieldName); |
||
258 | $this->flushNow('... ERROR: can not find best ' . $fieldName . ' for ' . $tableName . '.ID = ' . $row['ID'] . ' current value: ' . $row[$fieldName] . ' we recommend: ' . $bestValue, 'error'); |
||
259 | $this->runUpdateQuery( |
||
260 | 'UPDATE "' . $tableName . '" |
||
261 | SET "' . $tableName . '"."' . $fieldName . '" = \'' . addslashes($bestValue) . '\' |
||
262 | WHERE ID = ' . $row['ID'], |
||
263 | 2 |
||
264 | ); |
||
265 | } |
||
266 | } |
||
267 | } else { |
||
268 | $this->flushNow('... Updating "' . $tableName . '"."' . $fieldName . '" TO NULL WHERE ' . $where, 'created'); |
||
269 | if ($this->forReal) { |
||
270 | $this->runUpdateQuery('UPDATE "' . $tableName . '" SET "' . $fieldName . '" = \'\' WHERE ' . $where, 2); |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | } |
||
275 | //run again with versioned tables ... |
||
276 | if (false === $versionedTable) { |
||
277 | foreach (['_Live', '_Versions'] as $extension) { |
||
278 | $testTable = $tableName . $extension; |
||
279 | if ($this->tableExists($testTable)) { |
||
280 | $this->fixClassNames($testTable, $objectClassName, $fieldName, true); |
||
281 | } else { |
||
282 | $this->flushNow('... ... there is no table called: ' . $testTable); |
||
283 | } |
||
284 | } |
||
285 | } |
||
286 | } |
||
287 | |||
288 | protected function fixFieldSize($tableName) |
||
289 | { |
||
290 | $databaseName = DB::get_conn()->getSelectedDatabase(); |
||
291 | DB::query('ALTER TABLE "' . $databaseName . '"."' . $tableName . '" CHANGE ClassName ClassName VARCHAR(255);'); |
||
292 | } |
||
293 | |||
294 | protected function bestClassName(string $objectClassName, string $tableName, string $fieldName): string |
||
334 | } |
||
335 | } |
||
336 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths