| Total Complexity | 62 | 
| Total Lines | 427 | 
| Duplicated Lines | 0 % | 
| Changes | 14 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like RunForOneObject 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 RunForOneObject, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 20 | class RunForOneObject | ||
| 21 | { | ||
| 22 | use Configurable; | ||
| 23 | use Injectable; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Versioned DataObject. | ||
| 27 | * | ||
| 28 | * @var DataObject | ||
| 29 | */ | ||
| 30 | protected $object; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * array of Version numbers to delete. | ||
| 34 | * | ||
| 35 | * @var array | ||
| 36 | */ | ||
| 37 | protected $toDelete = []; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * reversed list of templates (most specific first). | ||
| 41 | * | ||
| 42 | * @var array | ||
| 43 | */ | ||
| 44 | protected $templatesAvailable = []; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * list of tables to delete per class name. | ||
| 48 | * | ||
| 49 | * @var array | ||
| 50 | */ | ||
| 51 | protected $tablesPerClassName = []; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * list of templates per class name. | ||
| 55 | * | ||
| 56 | * @var array | ||
| 57 | */ | ||
| 58 | protected $templatesPerClassName = []; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * @var bool | ||
| 62 | */ | ||
| 63 | protected $verbose = false; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * @var bool | ||
| 67 | */ | ||
| 68 | protected $dryRun = false; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * @var array | ||
| 72 | */ | ||
| 73 | protected $countPerTableRegister = []; | ||
| 74 | |||
| 75 | /** | ||
| 76 | * schema is: | ||
| 77 | * ```php | ||
| 78 | * ClassName => [ | ||
| 79 | * PruningTemplateClassName1 => [ | ||
| 80 | * "PropertyName1" => Value1 | ||
| 81 | * "PropertyName2" => Value2 | ||
| 82 | * ], | ||
| 83 | * PruningTemplateClassName2 => [ | ||
| 84 | * ], | ||
| 85 | * ] | ||
| 86 | * ```. | ||
| 87 | * N.B. least specific first! | ||
| 88 | * | ||
| 89 | * @var array | ||
| 90 | */ | ||
| 91 | private static $templates = [ | ||
| 92 | 'default' => [ | ||
| 93 | BasedOnTimeScale::class => [], | ||
| 94 | ], | ||
| 95 | SiteTree::class => [ | ||
| 96 | Drafts::class => [], | ||
| 97 | SiteTreeVersioningTemplate::class => [], | ||
| 98 | ], | ||
| 99 | File::class => [ | ||
| 100 | DeleteFiles::class => [], | ||
| 101 | // OnlyLastOnes::class => [], | ||
| 102 | ], | ||
| 103 | ]; | ||
| 104 | |||
| 105 | public function __construct() | ||
| 106 |     { | ||
| 107 | $this->gatherTemplates(); | ||
| 108 | } | ||
| 109 | |||
| 110 | public static function inst() | ||
| 111 |     { | ||
| 112 | return Injector::inst()->get(static::class); | ||
| 113 | } | ||
| 114 | |||
| 115 | public function setVerbose(?bool $verbose = true): self | ||
| 116 |     { | ||
| 117 | $this->verbose = $verbose; | ||
| 118 | |||
| 119 | return $this; | ||
| 120 | } | ||
| 121 | |||
| 122 | public function setDryRun(?bool $dryRun = true): self | ||
| 123 |     { | ||
| 124 | $this->dryRun = $dryRun; | ||
| 125 | |||
| 126 | return $this; | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * returns the total number deleted. | ||
| 131 | * | ||
| 132 | * @param DataObject $object | ||
| 133 | */ | ||
| 134 | public function getTableSizes($object, ?bool $lastOnly = false): array | ||
| 135 |     { | ||
| 136 | $this->object = $object; | ||
| 137 | $array = []; | ||
| 138 |         if ($this->isValidObject()) { | ||
| 139 | $queriedTables = $this->getTablesForClassName(); | ||
| 140 | // print_r($this->toDelete[$this->getUniqueKey()]); | ||
| 141 |             foreach ($queriedTables as $table) { | ||
| 142 | $array[$table] = $this->getCountPerTable($table); | ||
| 143 | } | ||
| 144 | } | ||
| 145 |         if (count($array) && $lastOnly) { | ||
| 146 | $lastKey = array_key_last($array); | ||
| 147 | |||
| 148 | return [ | ||
| 149 | $lastKey => $array[$lastKey], | ||
| 150 | ]; | ||
| 151 | } | ||
| 152 | |||
| 153 | return $array; | ||
| 154 | } | ||
| 155 | |||
| 156 | public function getRootTable(string $className): ?string | ||
| 157 |     { | ||
| 158 |         if (class_exists($className)) { | ||
| 159 | $queriedTables = $this->getTablesForClassName($className); | ||
| 160 | // print_r($this->toDelete[$this->getUniqueKey()]); | ||
| 161 |             foreach ($queriedTables as $table) { | ||
| 162 | return $table; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | return null; | ||
| 167 | } | ||
| 168 | |||
| 169 | /** | ||
| 170 | * returns the total number deleted. | ||
| 171 | * | ||
| 172 | * @param DataObject $object | ||
| 173 | * | ||
| 174 | * @return int number of deletions | ||
| 175 | */ | ||
| 176 | public function deleteSuperfluousVersions($object): int | ||
| 177 |     { | ||
| 178 | $this->object = $object; | ||
| 179 |         if (! $this->isValidObject()) { | ||
| 180 | echo $object->ClassName . ' ERROR'; | ||
| 181 | |||
| 182 | return 0; | ||
| 183 | } | ||
| 184 | // reset to reduce size ... | ||
| 185 | $this->toDelete = []; | ||
| 186 | |||
| 187 | $this->workoutWhatNeedsDeleting(); | ||
| 188 | |||
| 189 | // Base table has Versioned data | ||
| 190 | $totalDeleted = 0; | ||
| 191 | |||
| 192 | // Ugly (borrowed from DataObject::class), but returns all | ||
| 193 | // database tables relating to DataObject | ||
| 194 | $queriedTables = $this->getTablesForClassName(); | ||
| 195 | // print_r($this->toDelete[$this->getUniqueKey()]); | ||
| 196 |         foreach ($queriedTables as $table) { | ||
| 197 | $overallCount = $this->getCountPerTable($table); | ||
| 198 |             if ($this->verbose) { | ||
| 199 | $selectToBeDeletedSQL = ' | ||
| 200 | SELECT COUNT(ID) AS C FROM "' . $table . '_Versions" | ||
| 201 | WHERE "RecordID" = ' . (int) $this->object->ID; | ||
| 202 | $totalRows = DB::query($selectToBeDeletedSQL)->value(); | ||
| 203 |                 DB::alteration_message('... ... ... Number of rows for current object in ' . $table . ': ' . $totalRows); | ||
| 204 | } | ||
| 205 |             if (count($this->toDelete[$this->getUniqueKey()]) > 0) { | ||
| 206 |                 if (true === $this->dryRun) { | ||
| 207 | $selectToBeDeletedSQL = ' | ||
| 208 | SELECT COUNT(ID) AS C FROM "' . $table . '_Versions" | ||
| 209 | WHERE | ||
| 210 |                             "Version" IN (' . implode(',', $this->toDelete[$this->getUniqueKey()]) . ') | ||
| 211 | AND "RecordID" = ' . (int) $this->object->ID; | ||
| 212 | |||
| 213 | $toBeDeletedCount = DB::query($selectToBeDeletedSQL)->value(); | ||
| 214 | $totalDeleted += $toBeDeletedCount; | ||
| 215 |                     if ($this->verbose) { | ||
| 216 |                         DB::alteration_message('... ... ... running ' . $selectToBeDeletedSQL); | ||
| 217 |                         DB::alteration_message('... ... ... total rows to be deleted  ... ' . $toBeDeletedCount . ' of ' . $overallCount); | ||
| 218 | } | ||
| 219 |                 } else { | ||
| 220 | $delSQL = ' | ||
| 221 | DELETE FROM "' . $table . '_Versions" | ||
| 222 | WHERE | ||
| 223 |                             "Version" IN (' . implode(',', $this->toDelete[$this->getUniqueKey()]) . ') | ||
| 224 | AND "RecordID" = ' . (int) $this->object->ID; | ||
| 225 | |||
| 226 | DB::query($delSQL); | ||
| 227 | $count = DB::affected_rows(); | ||
| 228 | $totalDeleted += $count; | ||
| 229 | $overallCount -= $count; | ||
| 230 |                     if ($this->verbose) { | ||
| 231 |                         DB::alteration_message('... ... ... running ' . $delSQL); | ||
| 232 |                         DB::alteration_message('... ... ... total rows deleted ... ' . $totalDeleted); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | $this->addCountRegister($table, $overallCount); | ||
| 237 | } | ||
| 238 | |||
| 239 | return $totalDeleted; | ||
| 240 | } | ||
| 241 | |||
| 242 | /** | ||
| 243 | * returns the total number deleted. | ||
| 244 | * | ||
| 245 | * @param DataObject $object | ||
| 246 | */ | ||
| 247 | public function getTemplatesDescription($object): array | ||
| 248 |     { | ||
| 249 | $array = []; | ||
| 250 | $this->object = $object; | ||
| 251 |         if ($this->isValidObject()) { | ||
| 252 | $myTemplates = $this->findBestSuitedTemplates(true); | ||
| 253 |             if (is_array($myTemplates) && count($myTemplates)) { | ||
| 254 |                 foreach ($myTemplates as $className => $options) { | ||
| 255 |                     if (class_exists($className)) { | ||
| 256 | $runner = new $className($this->object, []); | ||
| 257 | $array[] = $runner->getTitle() . ': ' . $runner->getDescription(); | ||
| 258 |                     } else { | ||
| 259 | $array[] = $options; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | return $array; | ||
| 266 | } | ||
| 267 | |||
| 268 | public function getCountRegister(): array | ||
| 271 | } | ||
| 272 | |||
| 273 | protected function gatherTemplates() | ||
| 274 |     { | ||
| 275 | $this->templatesAvailable = array_reverse( | ||
| 276 |             $this->Config()->get('templates'), | ||
| 277 | true //important - to preserve keys! | ||
| 278 | ); | ||
| 279 | // remove skips | ||
| 280 |         foreach ($this->templatesAvailable as $className => $runnerClassNameWithOptions) { | ||
| 281 |             if ('skip' === $runnerClassNameWithOptions) { | ||
| 282 | $this->templatesAvailable[$className] = 'skip'; | ||
| 283 | |||
| 284 | continue; | ||
| 285 | } | ||
| 286 |             if (is_array($runnerClassNameWithOptions)) { | ||
| 287 |                 foreach ($runnerClassNameWithOptions as $runnerClassName => $options) { | ||
| 288 |                     if ('skip' === $options) { | ||
| 289 | unset($this->templatesAvailable[$className][$runnerClassName]); | ||
| 290 | |||
| 291 | continue; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | protected function workoutWhatNeedsDeleting() | ||
| 299 |     { | ||
| 300 | // array of version IDs to delete | ||
| 301 | // IMPORTANT | ||
| 302 |         if (! isset($this->toDelete[$this->getUniqueKey()])) { | ||
| 303 | $this->toDelete[$this->getUniqueKey()] = []; | ||
| 304 | } | ||
| 305 | |||
| 306 | $myTemplates = $this->findBestSuitedTemplates(false); | ||
| 307 |         if (is_array($myTemplates) && $myTemplates !== []) { | ||
| 308 |             foreach ($myTemplates as $className => $options) { | ||
| 309 | $runner = new $className($this->object, $this->toDelete[$this->getUniqueKey()]); | ||
| 310 |                 if ($this->verbose) { | ||
| 311 |                     DB::alteration_message('... ... ... Running ' . $runner->getTitle() . ': ' . $runner->getDescription()); | ||
| 312 | } | ||
| 313 | |||
| 314 |                 foreach ($options as $key => $value) { | ||
| 315 | $method = 'set' . $key; | ||
| 316 |                     $runner->{$method}($value); | ||
| 317 | } | ||
| 318 | |||
| 319 | $runner->run(); | ||
| 320 | // print_r($runner->getToDelete()); | ||
| 321 | $this->toDelete[$this->getUniqueKey()] += $runner->getToDelete(); | ||
| 322 | |||
| 323 |                 if ($this->verbose) { | ||
| 324 |                     DB::alteration_message('... ... ... Total versions to delete now ' . count($this->toDelete[$this->getUniqueKey()])); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | /** | ||
| 331 | * we use this to make sure we never mix up two records. | ||
| 332 | */ | ||
| 333 | protected function getUniqueKey(): string | ||
| 334 |     { | ||
| 335 | return $this->object->ClassName . '_' . $this->object->ID; | ||
| 336 | } | ||
| 337 | |||
| 338 | protected function hasStages(): bool | ||
| 354 | } | ||
| 355 | |||
| 356 | protected function findBestSuitedTemplates(?bool $forExplanation = false) | ||
| 357 |     { | ||
| 358 |         $templates = $this->Config()->get('templates'); | ||
| 359 | $classesWithOptions = []; | ||
| 360 |         if (empty($this->templatesPerClassName[$this->object->ClassName]) || $forExplanation) { | ||
| 361 |             foreach ($this->templatesAvailable as $className => $classesWithOptions) { | ||
| 362 |                 if ($this->object instanceof $className) { | ||
| 363 |                     // if($forExplanation && $className !== $this->object->ClassName) { | ||
| 364 |                     //     echo "$className !== {$this->object->ClassName}"; | ||
| 365 | // $this->templatesPerClassName[$this->object->ClassName] = ['As '.$className]; | ||
| 366 | // } | ||
| 367 | $this->templatesPerClassName[$this->object->ClassName] = $classesWithOptions; | ||
| 368 | |||
| 369 | break; | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 |             if (! isset($this->templatesPerClassName[$this->object->ClassName])) { | ||
| 374 | $this->templatesPerClassName[$this->object->ClassName] = $templates['default'] ?? $classesWithOptions; | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | return $this->templatesPerClassName[$this->object->ClassName]; | ||
| 379 | } | ||
| 380 | |||
| 381 | protected function isValidObject(): bool | ||
| 382 |     { | ||
| 383 |         if (false === $this->hasStages()) { | ||
| 384 |             if ($this->verbose) { | ||
| 385 |                 DB::alteration_message('... ... ... Error, no stages', 'deleted'); | ||
| 386 | } | ||
| 387 | |||
| 388 | return false; | ||
| 389 | } | ||
| 390 | |||
| 391 |         // if (! $this->object->hasMethod('isLiveVersion')) { | ||
| 392 | // return false; | ||
| 393 | // } | ||
| 394 | // | ||
| 395 |         // if (false === $this->object->isLiveVersion()) { | ||
| 396 |         //     if ($this->verbose) { | ||
| 397 |         //         DB::alteration_message('... ... ... Error, not a live version', 'deleted'); | ||
| 398 | // } | ||
| 399 | // | ||
| 400 | // return false; | ||
| 401 | // } | ||
| 402 | // do not use exists here - as it has a different meaning for files and folders. | ||
| 403 | return $this->object && $this->object->ID; | ||
| 404 | } | ||
| 405 | |||
| 406 | protected function getTablesForClassName(?string $className = ''): array | ||
| 407 |     { | ||
| 408 |         if (! $className) { | ||
| 409 | $className = $this->object->ClassName; | ||
| 410 | } | ||
| 411 |         if (empty($this->tablesPerClassName[$className])) { | ||
| 412 | // $classTables = [] | ||
| 413 | // $allClasses = ClassInfo::subclassesFor($this->object->ClassName, true); | ||
| 414 |             // foreach ($allClasses as $class) { | ||
| 415 |             //     if (DataObject::getSchema()->classHasTable($class)) { | ||
| 416 | // $classTables[] = DataObject::getSchema()->tableName($class); | ||
| 417 | // } | ||
| 418 | // } | ||
| 419 | // $this->tablesPerClassName[$this->object->ClassName] = array_unique($classTables); | ||
| 420 | $id = $this->object->ID ?? 0; | ||
| 421 | $srcQuery = DataList::create($className) | ||
| 422 |                 ->filter('ID', intval($id)) | ||
| 423 | ->dataQuery() | ||
| 424 | ->query() | ||
| 425 | ; | ||
| 426 | $this->tablesPerClassName[$className] = $srcQuery->queriedTables(); | ||
| 427 | } | ||
| 428 | |||
| 429 | return $this->tablesPerClassName[$className]; | ||
| 430 | } | ||
| 431 | |||
| 432 | protected function addCountRegister(string $tableName, int $count): void | ||
| 433 |     { | ||
| 434 | $this->countPerTableRegister[$tableName] = $count; | ||
| 435 | } | ||
| 436 | |||
| 437 | protected function getCountPerTable(string $table): int | ||
| 447 | } | ||
| 448 | } | ||
| 449 | 
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