Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like DoctrineJobManager 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 DoctrineJobManager, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 15 | abstract class DoctrineJobManager extends BaseDoctrineJobManager |
||
| 16 | { |
||
| 17 | use ProgressCallbackTrait; |
||
| 18 | |||
| 19 | /** Number of seconds before a job is considered stalled if the runner is no longer active */ |
||
| 20 | const STALLED_SECONDS = 1800; |
||
| 21 | |||
| 22 | /** |
||
| 23 | * @param string $objectName |
||
| 24 | */ |
||
| 25 | abstract protected function countJobsByStatus($objectName, $status, $workerName = null, $method = null); |
||
| 26 | |||
| 27 | 2 | public function resetExceptionJobs($workerName = null, $method = null) |
|
| 45 | |||
| 46 | /** |
||
| 47 | * Sets the status to Job::STATUS_EXPIRED for those jobs that are expired. |
||
| 48 | * |
||
| 49 | * @param null $workerName |
||
| 50 | * @param null $method |
||
| 51 | * |
||
| 52 | * @return mixed |
||
| 53 | */ |
||
| 54 | abstract protected function updateExpired($workerName = null, $method = null); |
||
| 55 | |||
| 56 | 9 | protected function addWorkerNameMethod(array &$criterion, $workerName = null, $method = null) |
|
| 57 | { |
||
| 58 | 9 | if (null !== $workerName) { |
|
| 59 | 4 | $criterion['workerName'] = $workerName; |
|
| 60 | } |
||
| 61 | 9 | if (null !== $method) { |
|
| 62 | 4 | $criterion['method'] = $method; |
|
| 63 | } |
||
| 64 | 9 | } |
|
| 65 | |||
| 66 | 3 | public function pruneExpiredJobs($workerName = null, $method = null) |
|
| 67 | { |
||
| 68 | 3 | $count = $this->updateExpired($workerName, $method); |
|
| 69 | 3 | $criterion = ['status' => Job::STATUS_EXPIRED]; |
|
| 70 | 3 | $this->addWorkerNameMethod($criterion, $workerName, $method); |
|
| 71 | 3 | $objectManager = $this->getObjectManager(); |
|
| 72 | 3 | $repository = $this->getRepository(); |
|
| 73 | 3 | $finalCount = 0; |
|
| 74 | |||
| 75 | 3 | $metadata = $this->getObjectManager()->getClassMetadata($this->getJobClass()); |
|
| 76 | 3 | $identifierData = $metadata->getIdentifier(); |
|
| 77 | 3 | $idColumn = isset($identifierData[0]) ? $identifierData[0] : 'id'; |
|
| 78 | |||
| 79 | 3 | $fetchCount = $this->getFetchCount($count); |
|
| 80 | 3 | for ($i = 0; $i < $count; $i += $fetchCount) { |
|
| 81 | 3 | $expiredJobs = $repository->findBy($criterion, [$idColumn => 'ASC'], $fetchCount, $i); |
|
| 82 | 3 | $innerCount = 0; |
|
| 83 | 3 | if (!empty($expiredJobs)) { |
|
| 84 | 3 | foreach ($expiredJobs as $expiredJob) { |
|
| 85 | /* @var Job $expiredJob */ |
||
| 86 | 3 | $expiredJob->setStatus(Job::STATUS_EXPIRED); |
|
| 87 | 3 | $objectManager->remove($expiredJob); |
|
| 88 | 3 | ++$finalCount; |
|
| 89 | 3 | ++$innerCount; |
|
| 90 | } |
||
| 91 | } |
||
| 92 | 3 | $this->flush(); |
|
| 93 | 3 | for ($j = 0; $j < $innerCount; ++$j) { |
|
| 94 | 3 | $this->jobTiminigManager->recordTiming(JobTiming::STATUS_FINISHED_EXPIRED); |
|
| 95 | } |
||
| 96 | } |
||
| 97 | |||
| 98 | 3 | return $finalCount; |
|
| 99 | } |
||
| 100 | |||
| 101 | 4 | protected function getStalledJobs($workerName = null, $method = null) |
|
| 112 | |||
| 113 | 4 | protected function findRunningJobs($criterion, $count) |
|
| 114 | { |
||
| 115 | 4 | $repository = $this->getRepository(); |
|
| 116 | 4 | $runningJobsById = []; |
|
| 117 | |||
| 118 | 4 | $metadata = $this->getObjectManager()->getClassMetadata($this->getJobClass()); |
|
| 119 | 4 | $identifierData = $metadata->getIdentifier(); |
|
| 120 | 4 | $idColumn = isset($identifierData[0]) ? $identifierData[0] : 'id'; |
|
| 121 | |||
| 122 | 4 | $fetchCount = $this->getFetchCount($count); |
|
| 123 | 4 | for ($i = 0; $i < $count; $i += $fetchCount) { |
|
| 124 | 4 | $runningJobs = $repository->findBy($criterion, [$idColumn => 'ASC'], $fetchCount, $i); |
|
| 125 | 4 | if (!empty($runningJobs)) { |
|
| 126 | 4 | foreach ($runningJobs as $job) { |
|
| 127 | /** @var StallableJob $job */ |
||
| 128 | 4 | $runId = $job->getRunId(); |
|
| 129 | 4 | $runningJobsById[$runId][] = $job; |
|
| 130 | } |
||
| 131 | } |
||
| 132 | } |
||
| 133 | |||
| 134 | 4 | return $runningJobsById; |
|
| 135 | } |
||
| 136 | |||
| 137 | /** |
||
| 138 | * @param $runId |
||
| 139 | * @param array $jobs |
||
| 140 | * @param array $stalledJobs |
||
| 141 | */ |
||
| 142 | 4 | protected function extractStalledLiveRuns($runId, array $jobs, array &$stalledJobs) |
|
| 143 | { |
||
| 144 | 4 | $objectManager = $this->getObjectManager(); |
|
| 145 | 4 | $runRepository = $objectManager->getRepository($this->getRunManager()->getRunClass()); |
|
| 146 | 4 | if ($run = $runRepository->find($runId)) { |
|
| 147 | 2 | foreach ($jobs as $job) { |
|
| 148 | 2 | if ($run->getCurrentJobId() == $job->getId()) { |
|
| 149 | 2 | continue; |
|
| 150 | } |
||
| 151 | 2 | $stalledJobs[] = $job; |
|
| 152 | } |
||
| 153 | } |
||
| 154 | 4 | } |
|
| 155 | |||
| 156 | /** |
||
| 157 | * @param array $runningJobsById |
||
| 158 | * |
||
| 159 | * @return array |
||
| 160 | */ |
||
| 161 | 4 | protected function extractStalledJobs(array $runningJobsById) |
|
| 162 | { |
||
| 163 | 4 | $stalledJobs = []; |
|
| 164 | 4 | foreach (array_keys($runningJobsById) as $runId) { |
|
| 165 | 4 | if (!$runId && 0 !== $runId) { |
|
| 166 | 2 | $stalledJobs = array_merge($stalledJobs, $runningJobsById[$runId]); |
|
| 167 | 2 | continue; |
|
| 168 | } |
||
| 169 | 4 | $this->extractStalledLiveRuns($runId, $runningJobsById[$runId], $stalledJobs); |
|
| 170 | 4 | $this->extractStalledJobsRunArchive($runningJobsById, $stalledJobs, $runId); |
|
| 171 | } |
||
| 172 | |||
| 173 | 4 | return $stalledJobs; |
|
| 174 | } |
||
| 175 | |||
| 176 | 4 | protected function extractStalledJobsRunArchive(array $runningJobsById, array &$stalledJobs, $runId) |
|
| 177 | { |
||
| 178 | 4 | $runManager = $this->getRunManager(); |
|
| 179 | 4 | if (!method_exists($runManager, 'getObjectManager')) { |
|
| 180 | return; |
||
| 181 | } |
||
| 182 | 4 | if (!method_exists($runManager, 'getRunArchiveClass')) { |
|
| 183 | return; |
||
| 184 | } |
||
| 185 | |||
| 186 | /** @var EntityRepository|DocumentRepository $runArchiveRepository */ |
||
| 187 | 4 | $runArchiveRepository = $runManager->getObjectManager()->getRepository($runManager->getRunArchiveClass()); |
|
| 188 | /** @var Run $run */ |
||
| 189 | 4 | if ($run = $runArchiveRepository->find($runId)) { |
|
| 190 | 4 | if ($endTime = $run->getEndedAt()) { |
|
| 191 | // Did it end over an hour ago |
||
| 192 | 4 | if ((time() - $endTime->getTimestamp()) > static::STALLED_SECONDS) { |
|
| 193 | 4 | $stalledJobs = array_merge($stalledJobs, $runningJobsById[$runId]); |
|
| 194 | } |
||
| 195 | } |
||
| 196 | } |
||
| 197 | 4 | } |
|
| 198 | |||
| 199 | /** |
||
| 200 | * @param int $i |
||
| 201 | * @param int $count |
||
| 202 | * @param int $saveCount |
||
| 203 | * @param array $stalledJobs |
||
| 204 | * |
||
| 205 | * @return int |
||
| 206 | */ |
||
| 207 | 2 | protected function runStalledLoop($i, $count, $saveCount, array $stalledJobs) |
|
| 221 | |||
| 222 | 2 | public function resetStalledJobs($workerName = null, $method = null, callable $progressCallback = null) |
|
| 241 | |||
| 242 | /** |
||
| 243 | * @param string $workerName |
||
| 244 | * @param string $method |
||
| 245 | * @param callable|null $progressCallback |
||
| 246 | */ |
||
| 247 | 4 | public function pruneStalledJobs($workerName = null, $method = null, callable $progressCallback = null) |
|
| 269 | |||
| 270 | 8 | protected function stallableSaveHistory(StallableJob $job, $retry) |
|
| 278 | |||
| 279 | 43 | protected function stallableSave(StallableJob $job) |
|
| 299 | |||
| 300 | abstract protected function updateNearestBatch(Job $job); |
||
| 301 | |||
| 302 | /** |
||
| 303 | * @param string $objectName |
||
| 304 | */ |
||
| 305 | abstract protected function stopIdGenerator($objectName); |
||
| 306 | |||
| 307 | abstract protected function restoreIdGenerator($objectName); |
||
| 308 | |||
| 309 | /** |
||
| 310 | * @param array $criterion |
||
| 311 | * @param int $limit |
||
| 312 | * @param int $offset |
||
| 313 | */ |
||
| 314 | 2 | private function resetJobsByCriterion( |
|
| 342 | |||
| 343 | 22 | protected function resetSaveOk($function) |
|
| 346 | |||
| 347 | /** |
||
| 348 | * @param null $workerName |
||
| 349 | * @param null $methodName |
||
| 350 | * @param bool $prioritize |
||
| 351 | */ |
||
| 352 | abstract public function getJobQueryBuilder($workerName = null, $methodName = null, $prioritize = true); |
||
| 353 | |||
| 354 | /** |
||
| 355 | * @param StallableJob $jobArchive |
||
| 356 | * @param $className |
||
| 357 | * |
||
| 358 | * @return int Number of jobs reset |
||
| 359 | */ |
||
| 360 | 2 | protected function resetArchiveJob(StallableJob $jobArchive) |
|
| 379 | |||
| 380 | 8 | View Code Duplication | protected function resetJob(RetryableJob $job) |
| 395 | |||
| 396 | abstract public function getWorkersAndMethods(); |
||
| 397 | |||
| 398 | abstract public function countLiveJobs($workerName = null, $methodName = null); |
||
| 399 | |||
| 400 | abstract public function archiveAllJobs($workerName = null, $methodName = null, callable $progressCallback = null); |
||
| 401 | } |
||
| 402 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.