| Total Complexity | 81 |
| Total Lines | 388 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like AbstractQuery 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 AbstractQuery, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 23 | abstract class AbstractQuery extends ListQueryParameterObject implements CommandInterface |
||
| 24 | { |
||
| 25 | public const SORTORDER_ASC = "asc"; |
||
| 26 | public const SORTORDER_DESC = "desc"; |
||
| 27 | |||
| 28 | protected const RESULT_TYPES = [ |
||
| 29 | 'LIST' => 'LIST', 'LIST_PAGE' => 'LIST_PAGE', 'LIST_IDS' => 'LIST_IDS', 'LIST_DEPLOYMENT_ID_MAPPINGS' => 'LIST_DEPLOYMENT_ID_MAPPINGS', 'SINGLE_RESULT' => 'SINGLE_RESULT', 'COUNT' => 'COUNT' |
||
| 30 | ]; |
||
| 31 | |||
| 32 | protected $commandExecutor; |
||
| 33 | |||
| 34 | protected $resultType; |
||
| 35 | |||
| 36 | protected $expressions = []; |
||
| 37 | |||
| 38 | protected $validators = []; |
||
| 39 | |||
| 40 | protected $maxResultsLimitEnabled; |
||
| 41 | |||
| 42 | public function __construct(CommandExecutorInterface $commandExecutor = null) |
||
| 43 | { |
||
| 44 | if ($commandExecutor !== null) { |
||
| 45 | $this->commandExecutor = $commandExecutor; |
||
| 46 | |||
| 47 | // all queries that are created with a dedicated command executor |
||
| 48 | // are treated as adhoc queries (i.e. queries not created in the context |
||
| 49 | // of a command) |
||
| 50 | $this->addValidator(AdhocQueryValidator::instance()); |
||
| 51 | } |
||
| 52 | } |
||
| 53 | |||
| 54 | public function setCommandExecutor(CommandExecutorInterface $commandExecutor): AbstractQuery |
||
| 55 | { |
||
| 56 | $this->commandExecutor = $commandExecutor; |
||
| 57 | return $this; |
||
| 58 | } |
||
| 59 | |||
| 60 | public function orderBy(QueryPropertyInterface $property): QueryInterface |
||
| 61 | { |
||
| 62 | if ($property instanceof QueryPropertyInterface) { |
||
| 63 | return $this->orderBy(new QueryOrderingProperty(null, $property)); |
||
| 64 | } elseif ($property instanceof QueryOrderingProperty) { |
||
| 65 | $this->orderingProperties[] = $property; |
||
| 66 | return $this; |
||
| 67 | } |
||
| 68 | } |
||
| 69 | |||
| 70 | public function asc(): QueryInterface |
||
| 71 | { |
||
| 72 | return $this->direction(Direction::ascending()); |
||
| 73 | } |
||
| 74 | |||
| 75 | public function desc(): QueryInterface |
||
| 76 | { |
||
| 77 | return $this->direction(Direction::descending()); |
||
| 78 | } |
||
| 79 | |||
| 80 | public function direction(Direction $direction): QueryInterface |
||
| 81 | { |
||
| 82 | $currentOrderingProperty = null; |
||
| 83 | |||
| 84 | if (!empty($this->orderingProperties)) { |
||
| 85 | $currentOrderingProperty = $this->orderingProperties[count($this->orderingProperties) - 1]; |
||
| 86 | } |
||
| 87 | |||
| 88 | EnsureUtil::ensureNotNull("You should call any of the orderBy methods first before specifying a direction", "currentOrderingProperty", $currentOrderingProperty); |
||
| 89 | |||
| 90 | if ($currentOrderingProperty->getDirection() != null) { |
||
| 91 | EnsureUtil::ensureNull("Invalid query: can specify only one direction desc() or asc() for an ordering constraint", "direction", $direction); |
||
| 92 | } |
||
| 93 | |||
| 94 | $currentOrderingProperty->setDirection($direction); |
||
| 95 | return $this; |
||
| 96 | } |
||
| 97 | |||
| 98 | protected function checkQueryOk(): void |
||
| 99 | { |
||
| 100 | foreach ($this->orderingProperties as $orderingProperty) { |
||
| 101 | EnsureUtil::ensureNotNull("Invalid query: call asc() or desc() after using orderByXX()", "direction", $orderingProperty->getDirection()); |
||
| 102 | } |
||
| 103 | } |
||
| 104 | |||
| 105 | public function singleResult() |
||
| 106 | { |
||
| 107 | $this->resultType = self::RESULT_TYPES['SINGLE_RESULT']; |
||
| 108 | return $this->executeResult($this->resultType); |
||
| 109 | } |
||
| 110 | |||
| 111 | public function list(): array |
||
| 112 | { |
||
| 113 | $this->resultType = self::RESULT_TYPES['LIST']; |
||
| 114 | return $this->executeResult($this->resultType); |
||
| 115 | } |
||
| 116 | |||
| 117 | public function listPage(int $firstResult, int $maxResults): array |
||
| 118 | { |
||
| 119 | $this->firstResult = $firstResult; |
||
| 120 | $this->maxResults = $maxResults; |
||
| 121 | $this->resultType = self::RESULT_TYPES['LIST_PAGE']; |
||
| 122 | return $this->executeResult($this->resultType); |
||
| 123 | } |
||
| 124 | |||
| 125 | public function executeResult(string $resultType) |
||
| 126 | { |
||
| 127 | if ($this->commandExecutor != null) { |
||
| 128 | if (!$this->maxResultsLimitEnabled) { |
||
| 129 | $this->maxResultsLimitEnabled = Context::getCommandContext() == null; |
||
| 130 | } |
||
| 131 | return $this->commandExecutor->execute($this); |
||
| 132 | } |
||
| 133 | |||
| 134 | switch ($resultType) { |
||
| 135 | case self::RESULT_TYPES['SINGLE_RESULT']: |
||
| 136 | return executeSingleResult(Context::getCommandContext()); |
||
| 137 | case self::RESULT_TYPES['LIST_PAGE']: |
||
| 138 | case self::RESULT_TYPES['LIST']: |
||
| 139 | return $this->evaluateExpressionsAndExecuteList(Context::getCommandContext(), null); |
||
| 140 | default: |
||
| 141 | throw new ProcessEngineException("Unknown result type!"); |
||
| 142 | } |
||
| 143 | } |
||
| 144 | |||
| 145 | public function count(): int |
||
| 152 | } |
||
| 153 | |||
| 154 | public function unlimitedList(): array |
||
| 155 | { |
||
| 156 | $this->resultType = self::RESULT_TYPES['LIST']; |
||
| 157 | if ($this->commandExecutor != null) { |
||
| 158 | return $this->commandExecutor->execute($this); |
||
| 159 | } |
||
| 160 | return $this->evaluateExpressionsAndExecuteList(Context::getCommandContext(), null); |
||
| 161 | } |
||
| 162 | |||
| 163 | public function execute(CommandContext $commandContext) |
||
| 164 | { |
||
| 165 | if ($this->resultType == self::RESULT_TYPES['LIST']) { |
||
| 166 | return $this->evaluateExpressionsAndExecuteList($commandContext, null); |
||
| 167 | } elseif ($this->resultType == self::RESULT_TYPES['SINGLE_RESULT']) { |
||
| 168 | return $this->executeSingleResult($commandContext); |
||
| 169 | } elseif ($this->resultType == self::RESULT_TYPES['LIST_PAGE']) { |
||
| 170 | return $this->evaluateExpressionsAndExecuteList($commandContext, null); |
||
| 171 | } elseif ($this->resultType == self::RESULT_TYPES['LIST_IDS']) { |
||
| 172 | return $this->evaluateExpressionsAndExecuteIdsList($commandContext); |
||
| 173 | } elseif ($this->resultType == self::RESULT_TYPES['LIST_DEPLOYMENT_ID_MAPPINGS']) { |
||
| 174 | return $this->evaluateExpressionsAndExecuteDeploymentIdMappingsList($commandContext); |
||
| 175 | } else { |
||
| 176 | return $this->evaluateExpressionsAndExecuteCount($commandContext); |
||
| 177 | } |
||
| 178 | } |
||
| 179 | |||
| 180 | public function evaluateExpressionsAndExecuteCount(CommandContext $commandContext): int |
||
| 181 | { |
||
| 182 | $this->validate(); |
||
| 183 | $this->evaluateExpressions(); |
||
| 184 | return !$this->hasExcludingConditions() ? $this->executeCount($commandContext) : 0; |
||
| 185 | } |
||
| 186 | |||
| 187 | abstract public function executeCount(CommandContext $commandContext): int; |
||
| 188 | |||
| 189 | public function evaluateExpressionsAndExecuteList(CommandContext $commandContext, Page $page): array |
||
| 190 | { |
||
| 191 | $this->checkMaxResultsLimit(); |
||
| 192 | $this->validate(); |
||
| 193 | $this->evaluateExpressions(); |
||
| 194 | return !$this->hasExcludingConditions() ? $this->executeList($commandContext, $page) : []; |
||
| 195 | } |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Whether or not the query has excluding conditions. If the query has excluding conditions, |
||
| 199 | * (e.g. task due date before and after are excluding), the SQL query is avoided and a default result is |
||
| 200 | * returned. The returned result is the same as if the SQL was executed and there were no entries. |
||
| 201 | * |
||
| 202 | * @return {@code true} if the query does have excluding conditions, {@code false} otherwise |
||
| 203 | */ |
||
| 204 | protected function hasExcludingConditions(): bool |
||
| 205 | { |
||
| 206 | return false; |
||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Executes the actual query to retrieve the list of results. |
||
| 211 | * @param page used if the results must be paged. If null, no paging will be applied. |
||
| 212 | */ |
||
| 213 | abstract public function executeList(CommandContext $commandContext, Page $page): array; |
||
| 214 | |||
| 215 | public function executeSingleResult(CommandContext $commandContext) |
||
| 216 | { |
||
| 217 | $this->disableMaxResultsLimit(); |
||
| 218 | $results = $this->evaluateExpressionsAndExecuteList($commandContext, new Page(0, 2)); |
||
| 219 | if (count($results) == 1) { |
||
| 220 | return $results[0]; |
||
| 221 | } elseif (count($results) > 1) { |
||
| 222 | throw new ProcessEngineException("Query return " . count($results) . " results instead of max 1"); |
||
| 223 | } |
||
| 224 | return null; |
||
| 225 | } |
||
| 226 | |||
| 227 | public function getExpressions(): array |
||
| 228 | { |
||
| 229 | return $this->expressions; |
||
| 230 | } |
||
| 231 | |||
| 232 | public function setExpressions(array $expressions): void |
||
| 233 | { |
||
| 234 | $this->expressions = $expressions; |
||
| 235 | } |
||
| 236 | |||
| 237 | public function addExpression(string $key, string $expression): void |
||
| 238 | { |
||
| 239 | $this->expressions[$key] = $expression; |
||
| 240 | } |
||
| 241 | |||
| 242 | protected function evaluateExpressions(): void |
||
| 243 | { |
||
| 244 | // we cannot iterate directly on the entry set cause the expressions |
||
| 245 | // are removed by the setter methods during the iteration |
||
| 246 | $entries = $this->expressions; |
||
| 247 | |||
| 248 | foreach ($entries as $methodName => $expression) { |
||
| 249 | $value = null; |
||
| 250 | |||
| 251 | try { |
||
| 252 | $value = Context::getProcessEngineConfiguration() |
||
| 253 | ->getExpressionManager() |
||
| 254 | ->createExpression($expression) |
||
| 255 | ->getValue(null); |
||
| 256 | } catch (ProcessEngineException $e) { |
||
| 257 | throw new ProcessEngineException("Unable to resolve expression '" . $expression . "' for method '" . $methodName . "' on class '" . get_class($this) . "'", $e); |
||
| 258 | } |
||
| 259 | |||
| 260 | // automatically convert DateTime to date |
||
| 261 | if ($value instanceof \DateTime) { |
||
| 262 | $value = $value->format('c'); |
||
| 263 | } |
||
| 264 | |||
| 265 | try { |
||
| 266 | $method = $this->getMethod($methodName); |
||
| 267 | $method->invoke($this, $value); |
||
| 268 | } catch (\Exception $e) { |
||
| 269 | throw new ProcessEngineException("Unable to access method '" . $methodName . "' on class '" . get_class($this) . "'", $e); |
||
| 270 | } |
||
| 271 | } |
||
| 272 | } |
||
| 273 | |||
| 274 | protected function getMethod(string $methodName): \ReflectionMethod |
||
| 275 | { |
||
| 276 | $ref = new ReflectionClass($this); |
||
| 277 | foreach ($ref->getMethods() as $method) { |
||
| 278 | if ($method->name == $methodName) { |
||
| 279 | return $method; |
||
| 280 | } |
||
| 281 | } |
||
| 282 | throw new ProcessEngineException("Unable to find method '" . $methodName . "' on class '" . get_class($this) . "'"); |
||
| 283 | } |
||
| 284 | |||
| 285 | public function extend(QueryInterface $extendingQuery) |
||
| 286 | { |
||
| 287 | throw new ProcessEngineException("Extending of query type '" . get_class($extendingQuery) . "' currently not supported"); |
||
| 288 | } |
||
| 289 | |||
| 290 | protected function mergeOrdering(AbstractQuery $extendedQuery, AbstractQuery $extendingQuery): void |
||
| 291 | { |
||
| 292 | $extendedQuery->orderingProperties = $this->orderingProperties; |
||
| 293 | if (!empty($extendingQuery->orderingProperties)) { |
||
| 294 | if (empty($extendedQuery->orderingProperties)) { |
||
| 295 | $extendedQuery->orderingProperties = $extendingQuery->orderingProperties; |
||
| 296 | } else { |
||
| 297 | $extendedQuery->orderingProperties = array_merge($extendedQuery->orderingProperties, $extendingQuer->orderingProperties); |
||
| 298 | } |
||
| 299 | } |
||
| 300 | } |
||
| 301 | |||
| 302 | protected function mergeExpressions(AbstractQuery $extendedQuery, AbstractQuery $extendingQuery): void |
||
| 303 | { |
||
| 304 | $mergedExpressions = $extendingQuery->getExpressions(); |
||
| 305 | foreach ($this->getExpressions() as $key => $value) { |
||
| 306 | if (!array_key_exists($key, $mergedExpressions)) { |
||
| 307 | $mergedExpressions[$key] = $value; |
||
| 308 | } |
||
| 309 | } |
||
| 310 | $extendedQuery->setExpressions($mergedExpressions); |
||
| 311 | } |
||
| 312 | |||
| 313 | public function validate(ValidatorInterface $validator = null): void |
||
| 314 | { |
||
| 315 | if ($validator !== null) { |
||
| 316 | $validator->validate($this); |
||
| 317 | } else { |
||
| 318 | foreach ($this->validators as $validator) { |
||
| 319 | $this->validate($validator); |
||
| 320 | } |
||
| 321 | } |
||
| 322 | } |
||
| 323 | |||
| 324 | public function addValidator(ValidatorInterface $validator): void |
||
| 325 | { |
||
| 326 | $this->validators[] = $validator; |
||
| 327 | } |
||
| 328 | |||
| 329 | public function removeValidator(ValidatorInterface $validator): void |
||
| 330 | { |
||
| 331 | foreach ($this->validators as $key => $curValidator) { |
||
| 332 | if ($curValidator == $validator) { |
||
| 333 | unset($this->validators[$key]); |
||
| 334 | } |
||
| 335 | } |
||
| 336 | } |
||
| 337 | |||
| 338 | public function listIds(): array |
||
| 339 | { |
||
| 340 | $this->resultType = self::RESULT_TYPES['LIST_IDS']; |
||
| 341 | $ids = []; |
||
| 342 | if ($this->commandExecutor != null) { |
||
| 343 | $ids = $this->commandExecutor->execute($this); |
||
| 344 | } else { |
||
| 345 | $ids = $this->evaluateExpressionsAndExecuteIdsList(Context::getCommandContext()); |
||
| 346 | } |
||
| 347 | |||
| 348 | if (!empty($ids)) { |
||
| 349 | QueryMaxResultsLimitUtil::checkMaxResultsLimit(count($ids)); |
||
| 350 | } |
||
| 351 | |||
| 352 | return $ids; |
||
| 353 | } |
||
| 354 | |||
| 355 | public function listDeploymentIdMappings(): array |
||
| 370 | } |
||
| 371 | |||
| 372 | public function evaluateExpressionsAndExecuteIdsList(CommandContext $commandContext): array |
||
| 373 | { |
||
| 374 | $this->validate(); |
||
| 375 | $this->evaluateExpressions(); |
||
| 376 | return !$this->hasExcludingConditions() ? $this->executeIdsList($commandContext) : []; |
||
| 377 | } |
||
| 378 | |||
| 379 | public function executeIdsList(CommandContext $commandContext): array |
||
| 380 | { |
||
| 381 | throw new UnsupportedOperationException("executeIdsList not supported by " . get_class($this)); |
||
| 382 | } |
||
| 383 | |||
| 384 | public function evaluateExpressionsAndExecuteDeploymentIdMappingsList(CommandContext $commandContext): array |
||
| 385 | { |
||
| 386 | $this->validate(); |
||
| 387 | $this->evaluateExpressions(); |
||
| 388 | return !$this->hasExcludingConditions() ? $this->executeDeploymentIdMappingsList($commandContext) : []; |
||
| 389 | } |
||
| 390 | |||
| 391 | public function executeDeploymentIdMappingsList(CommandContext $commandContext): array |
||
| 392 | { |
||
| 393 | throw new UnsupportedOperationException("executeDeploymentIdMappingsList not supported by " . get_class($this)); |
||
| 394 | } |
||
| 395 | |||
| 396 | protected function checkMaxResultsLimit(): void |
||
| 397 | { |
||
| 398 | if ($this->maxResultsLimitEnabled) { |
||
| 399 | QueryMaxResultsLimitUtil::checkMaxResultsLimit($this->maxResults); |
||
| 400 | } |
||
| 401 | } |
||
| 402 | |||
| 403 | public function enableMaxResultsLimit(): void |
||
| 404 | { |
||
| 405 | $this->maxResultsLimitEnabled = true; |
||
| 406 | } |
||
| 407 | |||
| 408 | public function disableMaxResultsLimit(): void |
||
| 411 | } |
||
| 412 | } |
||
| 413 |
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