CrankyDungBeetle   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
eloc 35
c 1
b 0
f 0
dl 0
loc 158
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getLogger() 0 3 1
A isPaused() 0 11 2
A dig() 0 20 4
A __construct() 0 10 1
A pause() 0 13 3
A getPause() 0 5 1
1
<?php
2
3
/*
4
 * This file is part of the Veslo project <https://github.com/symfony-doge/veslo>.
5
 *
6
 * (C) 2019 Pavel Petrov <[email protected]>.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://opensource.org/licenses/GPL-3.0 GPL-3.0
12
 */
13
14
declare(strict_types=1);
15
16
namespace Veslo\AnthillBundle\Vacancy\Digger;
17
18
use Psr\Cache\InvalidArgumentException;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\Cache\Adapter\AdapterInterface;
21
use Symfony\Component\Cache\CacheItem;
22
use Veslo\AnthillBundle\Vacancy\DiggerInterface;
23
use Veslo\AnthillBundle\Vacancy\Roadmap\ConveyorAwareRoadmap;
24
use Veslo\AppBundle\Workflow\Vacancy\WorkerInterface;
25
26
/**
27
 * Cranky dung beetle temporarily refuses to dig if it had too much unsuccessful attempts in a row
28
 * So stressful, be nice with him.
29
 *
30
 * @see DungBeetle
31
 */
32
class CrankyDungBeetle implements WorkerInterface, DiggerInterface
33
{
34
    /**
35
     * Cache for handling pause on too much unsuccessful search attempts and other values
36
     *
37
     * @const string
38
     */
39
    private const CACHE_NAMESPACE = 'veslo.anthill.vacancy.cranky_dung_beetle';
40
41
    /**
42
     * Cache instance for managing pause state of roadmaps
43
     *
44
     * @var AdapterInterface
45
     */
46
    private $cache;
47
48
    /**
49
     * Count of unsuccessful vacancy search attempts after which dung beetle will temporarily stops digging
50
     *
51
     * This prevents external website DDoSing and possible rate limiting
52
     *
53
     * @var int
54
     */
55
    private $attemptsUntilPause;
56
57
    /**
58
     * Digging pause duration (in seconds)
59
     *
60
     * Dung beetle will refuse dig command if it doesn't get
61
     * any vacancies by roadmap {$attemptsUntilPause} times in a row
62
     *
63
     * @var int
64
     */
65
    private $pauseDuration;
66
67
    /**
68
     * Provides base implementation of digging logic
69
     *
70
     * @var DungBeetle
71
     */
72
    private $_dungBeetle;
73
74
    /**
75
     * CrankyDungBeetle constructor.
76
     *
77
     * @param DungBeetle       $dungBeetle         Provides base implementation of digging logic
78
     * @param AdapterInterface $cache              Cache instance for managing pause state of roadmaps
79
     * @param int              $attemptsUntilPause Count of unsuccessful vacancy search attempts for pausing
80
     * @param int              $pauseDuration      Digging pause duration (in seconds)
81
     */
82
    public function __construct(
83
        DungBeetle $dungBeetle,
84
        AdapterInterface $cache,
85
        int $attemptsUntilPause,
86
        int $pauseDuration
87
    ) {
88
        $this->_dungBeetle        = $dungBeetle;
89
        $this->cache              = $cache;
90
        $this->attemptsUntilPause = $attemptsUntilPause;
91
        $this->pauseDuration      = $pauseDuration;
92
    }
93
94
    /**
95
     * Digs dung (vacancies) from internet by specified roadmap and attempts count
96
     * Temporarily stops digging if too much unsuccessful attempts occurred
97
     *
98
     * {@inheritdoc}
99
     */
100
    public function dig(ConveyorAwareRoadmap $roadmap, int $iterations = 1): int
101
    {
102
        if ($this->isPaused($roadmap)) {
103
            $logger = $this->getLogger();
104
105
            if ($logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
106
                $roadmapName = $roadmap->getName();
107
                $logger->debug('Roadmap is on pause.', ['roadmap' => $roadmapName]);
108
            }
109
110
            return 0;
111
        }
112
113
        $successfulIterations = $this->_dungBeetle->dig($roadmap, $iterations);
114
115
        if (empty($successfulIterations)) {
116
            $this->pause($roadmap);
117
        }
118
119
        return $successfulIterations;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function getLogger(): ?LoggerInterface
126
    {
127
        return $this->_dungBeetle->getLogger();
128
    }
129
130
    /**
131
     * Returns positive if roadmap is paused
132
     *
133
     * @param ConveyorAwareRoadmap $roadmap Provides URL of vacancies
134
     *
135
     * @return bool
136
     *
137
     * @throws InvalidArgumentException
138
     */
139
    private function isPaused(ConveyorAwareRoadmap $roadmap): bool
140
    {
141
        $pause = $this->getPause($roadmap->getName());
142
143
        if (!$pause->isHit()) {
144
            return false;
145
        }
146
147
        $unsuccessfulAttempts = (int) $pause->get();
148
149
        return $unsuccessfulAttempts >= $this->attemptsUntilPause;
150
    }
151
152
    /**
153
     * Pausing roadmap that makes it unusable by dung beetle for {$pauseDuration} seconds
154
     *
155
     * @param ConveyorAwareRoadmap $roadmap Provides URL of vacancies
156
     *
157
     * @return void
158
     *
159
     * @throws InvalidArgumentException
160
     */
161
    private function pause(ConveyorAwareRoadmap $roadmap): void
162
    {
163
        $pause = $this->getPause($roadmap->getName());
164
        $pause->expiresAfter($this->pauseDuration);
165
166
        $unsuccessfulAttempts = $pause->isHit() ? (int) $pause->get() : 0;
167
168
        if ($unsuccessfulAttempts >= $this->attemptsUntilPause) {
169
            return;
170
        }
171
172
        $pause->set($unsuccessfulAttempts + 1);
173
        $this->cache->save($pause);
174
    }
175
176
    /**
177
     * Returns cache item that represents pause state with unsuccessful attempts counter
178
     *
179
     * @param string $roadmapName Name of vacancy URL provider
180
     *
181
     * @return CacheItem
182
     *
183
     * @throws InvalidArgumentException
184
     */
185
    private function getPause(string $roadmapName): CacheItem
186
    {
187
        $cacheKey = implode('.', [static::CACHE_NAMESPACE, 'dig', $roadmapName]);
188
189
        return $this->cache->getItem($cacheKey);
190
    }
191
}
192