Complex classes like JobManager 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 JobManager, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class JobManager extends BaseJobManager |
||
16 | { |
||
17 | use CommonTrait; |
||
18 | protected static $saveInsertCalled = null; |
||
19 | protected static $resetInsertCalled = null; |
||
20 | |||
21 | public function countJobsByStatus($objectName, $status, $workerName = null, $method = null) |
||
22 | { |
||
23 | /** @var EntityManager $objectManager */ |
||
24 | $objectManager = $this->getObjectManager(); |
||
25 | |||
26 | $queryBuilder = $objectManager |
||
27 | ->createQueryBuilder() |
||
28 | ->select('count(a.id)') |
||
29 | ->from($objectName, 'a') |
||
30 | ->where('a.status = :status'); |
||
31 | |||
32 | if (null !== $workerName) { |
||
33 | $queryBuilder->andWhere('a.workerName = :workerName') |
||
34 | ->setParameter(':workerName', $workerName); |
||
35 | } |
||
36 | |||
37 | if (null !== $method) { |
||
38 | $queryBuilder->andWhere('a.method = :method') |
||
39 | ->setParameter(':method', $workerName); |
||
40 | } |
||
41 | |||
42 | $count = $queryBuilder->setParameter(':status', $status) |
||
43 | ->getQuery()->getSingleScalarResult(); |
||
44 | |||
45 | if (!$count) { |
||
46 | return 0; |
||
47 | } |
||
48 | |||
49 | return $count; |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * @param string|null $workerName |
||
54 | * @param string|null $method |
||
55 | * |
||
56 | * @return int Count of jobs pruned |
||
57 | */ |
||
58 | public function pruneErroneousJobs($workerName = null, $method = null) |
||
59 | { |
||
60 | /** @var EntityManager $objectManager */ |
||
61 | $objectManager = $this->getObjectManager(); |
||
62 | $queryBuilder = $objectManager->createQueryBuilder()->delete($this->getJobArchiveClass(), 'j'); |
||
63 | $queryBuilder->where('j.status = :status') |
||
64 | ->setParameter(':status', BaseJob::STATUS_ERROR); |
||
65 | |||
66 | $this->addWorkerNameCriterion($queryBuilder, $workerName, $method); |
||
67 | $query = $queryBuilder->getQuery(); |
||
68 | |||
69 | return intval($query->execute()); |
||
70 | } |
||
71 | |||
72 | protected function resetSaveOk($function) |
||
73 | { |
||
74 | $objectManager = $this->getObjectManager(); |
||
75 | $splObjectHash = spl_object_hash($objectManager); |
||
76 | |||
77 | if ('save' === $function) { |
||
78 | $compare = static::$resetInsertCalled; |
||
79 | } else { |
||
80 | $compare = static::$saveInsertCalled; |
||
81 | } |
||
82 | |||
83 | if ($splObjectHash === $compare) { |
||
84 | // Insert SQL is cached... |
||
85 | $msg = "Can't call save and reset within the same process cycle (or using the same EntityManager)"; |
||
86 | throw new LogicException($msg); |
||
87 | } |
||
88 | |||
89 | if ('save' === $function) { |
||
90 | static::$saveInsertCalled = spl_object_hash($objectManager); |
||
91 | } else { |
||
92 | static::$resetInsertCalled = spl_object_hash($objectManager); |
||
93 | } |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * @param string $workerName |
||
98 | * @param string $method |
||
99 | */ |
||
100 | protected function addWorkerNameCriterion(QueryBuilder $queryBuilder, $workerName = null, $method = null) |
||
101 | { |
||
102 | if (null !== $workerName) { |
||
103 | $queryBuilder->andWhere('j.workerName = :workerName')->setParameter(':workerName', $workerName); |
||
104 | } |
||
105 | |||
106 | if (null !== $method) { |
||
107 | $queryBuilder->andWhere('j.method = :method')->setParameter(':method', $method); |
||
108 | } |
||
109 | } |
||
110 | |||
111 | protected function updateExpired($workerName = null, $method = null) |
||
128 | |||
129 | /** |
||
130 | * Removes archived jobs older than $olderThan. |
||
131 | * |
||
132 | * @param \DateTime $olderThan |
||
133 | */ |
||
134 | public function pruneArchivedJobs(\DateTime $olderThan) |
||
142 | |||
143 | 1 | public function getJobCount($workerName = null, $method = null) |
|
188 | |||
189 | /** |
||
190 | * Get Jobs statuses. |
||
191 | */ |
||
192 | 1 | public function getStatus() |
|
212 | |||
213 | /** |
||
214 | * @param string $entityName |
||
215 | */ |
||
216 | 1 | protected function getStatusByEntityName($entityName, array &$result) |
|
238 | |||
239 | /** |
||
240 | * Get the next job to run (can be filtered by workername and method name). |
||
241 | * |
||
242 | * @param string $workerName |
||
243 | * @param string $methodName |
||
244 | * @param bool $prioritize |
||
245 | * |
||
246 | * @return Job|null |
||
247 | */ |
||
248 | public function getJob($workerName = null, $methodName = null, $prioritize = true, $runId = null) |
||
249 | { |
||
250 | do { |
||
251 | $queryBuilder = $this->getJobQueryBuilder($workerName, $methodName, $prioritize); |
||
252 | $queryBuilder->select('j.id'); |
||
253 | $queryBuilder->setMaxResults(100); |
||
254 | |||
255 | /** @var QueryBuilder $queryBuilder */ |
||
256 | $query = $queryBuilder->getQuery(); |
||
257 | $jobs = $query->getResult(); |
||
258 | if ($jobs) { |
||
259 | foreach ($jobs as $job) { |
||
260 | if ($job = $this->takeJob($job['id'])) { |
||
261 | return $job; |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | } while ($jobs); |
||
266 | |||
267 | return null; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * @param null $workerName |
||
272 | * @param null $methodName |
||
273 | * @param bool $prioritize |
||
274 | * |
||
275 | * @return QueryBuilder |
||
276 | */ |
||
277 | public function getJobQueryBuilder($workerName = null, $methodName = null, $prioritize = true) |
||
278 | { |
||
279 | /** @var EntityRepository $repository */ |
||
280 | $repository = $this->getRepository(); |
||
281 | $queryBuilder = $repository->createQueryBuilder('j'); |
||
282 | $this->addStandardPredicate($queryBuilder); |
||
283 | $this->addWorkerNameCriterion($queryBuilder, $workerName, $methodName); |
||
284 | |||
285 | if ($prioritize) { |
||
286 | $queryBuilder->addOrderBy('j.priority', 'DESC'); |
||
287 | $queryBuilder->addOrderBy('j.whenAt', 'ASC'); |
||
288 | } else { |
||
289 | $queryBuilder->orderBy('j.whenAt', 'ASC'); |
||
290 | } |
||
291 | |||
292 | return $queryBuilder; |
||
293 | } |
||
294 | |||
295 | protected function addStandardPredicate(QueryBuilder $queryBuilder) |
||
296 | { |
||
297 | $dateTime = new \DateTime(); |
||
298 | $queryBuilder |
||
299 | ->where('j.status = :status')->setParameter(':status', BaseJob::STATUS_NEW) |
||
300 | ->andWhere('j.locked is NULL') |
||
301 | ->andWhere($queryBuilder->expr()->orX( |
||
302 | $queryBuilder->expr()->isNull('j.whenAt'), |
||
303 | $queryBuilder->expr()->lte('j.whenAt', ':whenAt') |
||
304 | )) |
||
305 | ->andWhere($queryBuilder->expr()->orX( |
||
306 | $queryBuilder->expr()->isNull('j.expiresAt'), |
||
307 | $queryBuilder->expr()->gt('j.expiresAt', ':expiresAt') |
||
308 | )) |
||
309 | ->setParameter(':whenAt', $dateTime) |
||
310 | ->setParameter(':expiresAt', $dateTime); |
||
311 | } |
||
312 | |||
313 | protected function takeJob($jobId, $runId = null) |
||
314 | { |
||
315 | /** @var EntityRepository $repository */ |
||
316 | $repository = $this->getRepository(); |
||
317 | /** @var QueryBuilder $queryBuilder */ |
||
318 | $queryBuilder = $repository->createQueryBuilder('j'); |
||
319 | $queryBuilder |
||
320 | ->update() |
||
321 | ->set('j.locked', ':locked') |
||
322 | ->setParameter(':locked', true) |
||
323 | ->set('j.lockedAt', ':lockedAt') |
||
324 | ->setParameter(':lockedAt', new \DateTime()) |
||
325 | ->set('j.status', ':status') |
||
326 | ->setParameter(':status', BaseJob::STATUS_RUNNING); |
||
327 | if (null !== $runId) { |
||
328 | $queryBuilder |
||
329 | ->set('j.runId', ':runId') |
||
330 | ->setParameter(':runId', $runId); |
||
331 | } |
||
332 | $queryBuilder->where('j.id = :id'); |
||
333 | $queryBuilder->andWhere('j.locked is NULL'); |
||
334 | $queryBuilder->setParameter(':id', $jobId); |
||
335 | $resultCount = $queryBuilder->getQuery()->execute(); |
||
336 | |||
337 | if (1 === $resultCount) { |
||
338 | return $repository->find($jobId); |
||
339 | } |
||
340 | |||
341 | return null; |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Tries to update the nearest job as a batch. |
||
346 | * |
||
347 | * @param \Dtc\QueueBundle\Model\Job $job |
||
348 | * |
||
349 | * @return mixed|null |
||
350 | */ |
||
351 | public function updateNearestBatch(\Dtc\QueueBundle\Model\Job $job) |
||
377 | |||
378 | protected function updateBatchJob(Job $existingJob, $newPriority, $newWhenAt) |
||
407 | |||
408 | public function getWorkersAndMethods() |
||
428 | |||
429 | public function countLiveJobs($workerName = null, $methodName = null) |
||
440 | |||
441 | public function archiveAllJobs($workerName = null, $methodName = null, $progressCallback) |
||
458 | |||
459 | /** |
||
460 | * Move jobs in 'archive' status to the archive table. |
||
461 | * |
||
462 | * This is a bit of a hack to run a lower level query so as to process the INSERT INTO SELECT |
||
463 | * All on the server as "INSERT INTO SELECT" is not supported natively in Doctrine. |
||
464 | * |
||
465 | * @param null $workerName |
||
466 | * @param null $methodName |
||
467 | */ |
||
468 | protected function runArchive($workerName = null, $methodName = null, $progressCallback) |
||
497 | } |
||
498 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.