Issues (17)

src/Module.php (3 issues)

1
<?php
2
/**
3
 * @copyright Copyright(c) 2016 Webtools Ltd
4
 * @copyright Copyright(c) 2018 Thamtech, LLC
5
 * @link https://github.com/thamtech/yii2-scheduler
6
 * @license https://opensource.org/licenses/MIT
7
**/
8
9
namespace thamtech\scheduler;
10
11
use thamtech\scheduler\models\SchedulerLog;
12
use thamtech\scheduler\models\SchedulerTask;
13
use yii\base\BootstrapInterface;
14
use yii\base\Event;
15
use yii\di\Instance;
16
use yii\helpers\ArrayHelper;
17
use yii\mutex\Mutex;
18
use Yii;
19
20
/**
21
 * This is the main Yii2 Scheduler module class.
22
 *
23
 * @property Task[] $tasks definitions of tasks to be performed
24
 *
25
 * @property Mutex $mutex a mutex used to ensure that the task scheduler only
26
 * has one instance running at a time.
27
 */
28
class Module extends \yii\base\Module implements BootstrapInterface
29
{
30
    /**
31
     * @var string name of mutex to acquire before executing scheduled tasks.
32
     * This is only relevant if a mutex component has been set.
33
     */
34
    public $mutexName = self::class;
35
36
    /**
37
     * @var int time (in seconds) to wait for a lock to be released.
38
     * @see [[Mutex::acquire()]]
39
     */
40
    public $mutexTimeout = 5;
41
42
    /**
43
     * @var array task definitions
44
     */
45
    private $_taskDefinitions = [];
46
47
    /**
48
     * @var Task[] array of instantiate tasks
49
     */
50
    private $_taskInstances = [];
51
52
    /**
53
     * @var Mutex a mutex used to ensure that the task scheduler only has one
54
     * instance running at a time.
55
     */
56
    private $_mutex;
57
58
    /**
59
     * @var ErrorLogTarget the error log target component
60
     */
61
    private $_errorLogTarget;
62
63
    /**
64
     * Bootstrap the console controllers.
65
     *
66
     * @param \yii\base\Application $app
67
     */
68
    public function bootstrap($app)
69
    {
70
        Yii::setAlias('@scheduler', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src');
71
72
        if ($app instanceof \yii\console\Application && !isset($app->controllerMap[$this->id])) {
73
            $app->controllerMap[$this->id] = [
74
                'class' => 'thamtech\scheduler\console\SchedulerController',
75
                'scheduler' => $this,
76
            ];
77
        }
78
79
        $this->_errorLogTarget = Yii::createObject([
80
            'categories' => [
81
                'yii\base\ErrorException*',
82
            ],
83
            'class' => ErrorLogTarget::class,
84
            'levels' => ['error'],
85
            'logVars' => [],
86
            'enabled' => false,
87
        ]);
88
        $app->log->targets[self::class] = $this->_errorLogTarget;
89
90
        Event::on(Task::class, Task::EVENT_BEFORE_RUN, function ($event) {
91
            /* @var TaskEvent $event */
92
            $this->_errorLogTarget->enabled = true;
93
            $this->_errorLogTarget->taskRunner = $event->taskRunner;
94
        });
95
96
        Event::on(Task::class, Task::EVENT_AFTER_RUN, function ($event) {
0 ignored issues
show
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

96
        Event::on(Task::class, Task::EVENT_AFTER_RUN, function (/** @scrutinizer ignore-unused */ $event) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
97
            /* @var TaskEvent $event */
98
            $this->_errorLogTarget->enabled = false;
99
            $this->_errorLogTarget->taskRunner = null;
100
        });
101
    }
102
103
    /**
104
     * Sets the mutex component.
105
     *
106
     * @param Mutex|string|array|null $mutex a Mutex or reference to a Mutex. You may
107
     * specify the mutex in terms of a component ID, an Instance object, or
108
     * a configuration array for creating the Mutex component. If the "class"
109
     * value is not specified in the configuration array, it will use the value
110
     * of `yii\mutex\Mutex`.
111
     * Set null (default) if you would not like to require acquiring a mutex
112
     * before running scheduled tasks.
113
     */
114
    public function setMutex($mutex)
115
    {
116
        $this->_mutex = ($mutex === null) ? null : Instance::ensure($mutex, Mutex::class);
117
    }
118
119
    /**
120
     * Gets the mutex component.
121
     *
122
     * @return Mutex|null
123
     */
124
    public function getMutex()
125
    {
126
        return $this->_mutex;
127
    }
128
129
    /**
130
     * Acquires a lock to run scheduled tasks.
131
     *
132
     * @return bool lock acquiring result.
133
     *
134
     * @see [[Mutex::acquire()]]
135
     */
136
    public function acquireLock()
137
    {
138
        if (empty($this->mutex) || empty($this->mutexName)) {
139
            return true;
140
        }
141
142
        return $this->mutex->acquire($this->mutexName, $this->mutexTimeout);
143
    }
144
145
    /**
146
     * Releases acquired lock.
147
     *
148
     * @return bool lock release result: false in case named lock was not found.
149
     *
150
     * @see [[Mutex::release()]]
151
     */
152
    public function releaseLock()
153
    {
154
        if (empty($this->mutex) || empty($this->mutexName)) {
155
            return true;
156
        }
157
158
        return $this->mutex->release($this->mutexName);
159
    }
160
161
    /**
162
     * Sets the tasks for a list of Task configuration arrays or Task objects.
163
     *
164
     * @param array $taskDefinitions array of Task configurations or Task objects
165
     */
166 1
    public function setTasks(array $taskDefinitions)
167
    {
168 1
        $this->_taskDefinitions = ArrayHelper::merge($this->_taskDefinitions, $taskDefinitions);
169 1
    }
170
171
    /**
172
     * Gets Task instances.
173
     *
174
     * @return Task[]
175
     *
176
     * @throws \yii\base\ErrorException
177
     */
178 1
    public function getTasks()
179
    {
180 1
        $this->ensureTaskInstances();
181 1
        $this->cleanTasks();
182 1
        return $this->_taskInstances;
183
    }
184
185
    /**
186
     * Removes any records of tasks that no longer exist.
187
     */
188 1
    public function cleanTasks()
189
    {
190 1
        $this->ensureTaskInstances();
191
192 1
        foreach (SchedulerTask::find()->indexBy('name')->all() as $name => $task) { /* @var SchedulerTask $task */
193 1
            if (!array_key_exists($name, $this->_taskInstances)) {
194
                SchedulerLog::deleteAll(['scheduler_task_id' => $task->id]);
195 1
                $task->delete();
196
            }
197
        }
198 1
    }
199
200
    /**
201
     * Given the key of a task, it will return that task.
202
     * If the task doesn't exist, null will be returned.
203
     *
204
     * @param $name
205
     *
206
     * @return null|object
207
     *
208
     * @throws \yii\base\InvalidConfigException
209
     */
210
    public function loadTask($name)
211
    {
212
        $tasks = $this->tasks;
213
        return isset($tasks[$name]) ? $tasks[$name] : null;
214
    }
215
216
    /**
217
     * Makes sure that the defined tasks have been instantiated
218
     */
219 1
    private function ensureTaskInstances()
220
    {
221
        // remove instances that are no longer in
222 1
        $staleInstanceKeys = array_keys(array_diff_key($this->_taskInstances, $this->_taskDefinitions));
223 1
        foreach ($staleInstanceKeys as $name) {
224
            unset($this->_taskInstances[$name]);
225
        }
226
227
        $defaultTaskConfig = [
228 1
            'scheduler' => $this,
229
        ];
230
231
        // establish task instances that are defined but not yet instantiated
232 1
        $taskDefinitions = array_diff_key($this->_taskDefinitions, $this->_taskInstances);
233 1
        foreach ($taskDefinitions as $name=>$task) {
234 1
            if (!($task instanceof Task)) {
235 1
                $task = ArrayHelper::merge($defaultTaskConfig, $task);
236 1
                $task = Yii::createObject($task);
237
            }
238
239 1
            if (!($task instanceof Task)) {
240
                throw new InvalidConfigException('The task definition must define an instance of \thamtech\scheduler\Task.');
0 ignored issues
show
The type thamtech\scheduler\InvalidConfigException was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
241
            }
242
243 1
            $task->setModel(SchedulerTask::createTaskModel($name, $task));
0 ignored issues
show
It seems like thamtech\scheduler\model...TaskModel($name, $task) can also be of type array; however, parameter $model of thamtech\scheduler\Task::setModel() does only seem to accept thamtech\scheduler\models\SchedulerTask, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
            $task->setModel(/** @scrutinizer ignore-type */ SchedulerTask::createTaskModel($name, $task));
Loading history...
244 1
            $this->_taskInstances[$name] = $task;
245
        }
246 1
    }
247
}
248