Completed
Push — master ( a5a753...454108 )
by Wachter
07:05
created

ScheduleSystemTasksCommand::processSystemTask()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 18
cts 18
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 15
nc 5
nop 3
crap 5
1
<?php
2
3
namespace Task\TaskBundle\Command;
4
5
use Cron\CronExpression;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Task\Scheduler\TaskSchedulerInterface;
10
use Task\Storage\TaskExecutionRepositoryInterface;
11
use Task\TaskBundle\Builder\TaskBuilder;
12
use Task\TaskBundle\Entity\TaskRepository;
13
use Task\TaskInterface;
14
use Task\TaskStatus;
15
16
/**
17
 * Schedules configured system-tasks.
18
 */
19
class ScheduleSystemTasksCommand extends Command
20
{
21
    /**
22
     * @var array
23
     */
24
    private $systemTasks;
25
26
    /**
27
     * @var TaskSchedulerInterface
28
     */
29
    private $scheduler;
30
31
    /**
32
     * @var TaskRepository
33
     */
34
    private $taskRepository;
35
36
    /**
37
     * @var TaskExecutionRepositoryInterface
38
     */
39
    private $taskExecutionRepository;
40
41
    /**
42
     * @param string $name
43
     * @param array $systemTasks
44
     * @param TaskSchedulerInterface $scheduler
45
     * @param TaskRepository $taskRepository
46
     * @param TaskExecutionRepositoryInterface $taskExecutionRepository
47
     */
48 19
    public function __construct(
49
        $name,
50
        array $systemTasks,
51
        TaskSchedulerInterface $scheduler,
52
        TaskRepository $taskRepository,
53
        TaskExecutionRepositoryInterface $taskExecutionRepository
54
    ) {
55 19
        parent::__construct($name);
56
57 19
        $this->systemTasks = $systemTasks;
58 19
        $this->scheduler = $scheduler;
59 19
        $this->taskRepository = $taskRepository;
60 19
        $this->taskExecutionRepository = $taskExecutionRepository;
61 19
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 19
    protected function configure()
67
    {
68 19
        $this->setDescription('Schedule system-tasks')->setHelp(
69
            <<<'EOT'
70
The <info>%command.name%</info> command schedules configured system tasks.
71
72
    $ %command.full_name%
73
74
You can configure them by extending the <info>task.system_task</info> array in your config file.
75
EOT
76 19
        );
77 19
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 7
    protected function execute(InputInterface $input, OutputInterface $output)
83
    {
84 7
        $output->writeln(sprintf('Schedule %s system-tasks:', count($this->systemTasks)));
85 7
        $output->writeln('');
86
87 7
        foreach ($this->systemTasks as $systemKey => $systemTask) {
88
            try {
89 6
                $this->processSystemTask($systemKey, $systemTask, $output);
90 6
            } catch (\Exception $exception) {
91 1
                $output->writeln(
92 1
                    sprintf(
93 1
                        ' * System-task "%s" failed because of: <exception>%s</exception>',
94 1
                        $systemKey,
95 1
                        $exception->getMessage()
96 1
                    )
97 1
                );
98
            }
99 7
        }
100
101 7
        foreach ($this->taskRepository->findSystemTasks() as $task) {
102 7
            if (!in_array($task->getSystemKey(), array_keys($this->systemTasks)) && ($this->disableTask($task))) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Task\TaskInterface as the method getSystemKey() does only exist in the following implementations of said interface: Task\TaskBundle\Entity\Task.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
103 1
                $output->writeln(
104 1
                    sprintf(' * System-task "%s" was <comment>disabled</comment>', $task->getSystemKey())
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Task\TaskInterface as the method getSystemKey() does only exist in the following implementations of said interface: Task\TaskBundle\Entity\Task.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
105 1
                );
106 1
            }
107 7
        }
108
109 7
        $output->writeln('');
110 7
        $output->writeln('System-tasks successfully scheduled');
111 7
    }
112
113
    /**
114
     * Process single system task.
115
     *
116
     * @param string $systemKey
117
     * @param array $systemTask
118
     * @param OutputInterface $output
119
     */
120 6
    private function processSystemTask($systemKey, array $systemTask, OutputInterface $output)
121
    {
122 6
        if (!$systemTask['enabled']) {
123 1
            if ($this->disableSystemTask($systemKey)) {
124 1
                $output->writeln(sprintf(' * System-task "%s" was <comment>disabled</comment>', $systemKey));
125 1
            }
126
127 1
            return;
128
        }
129
130 5
        if ($task = $this->taskRepository->findBySystemKey($systemKey)) {
131 2
            $this->updateTask($systemKey, $systemTask, $task);
132
133 1
            $output->writeln(sprintf(' * System-task "%s" was <info>updated</info>', $systemKey));
134
135 1
            return;
136
        }
137
138
        /** @var TaskBuilder $builder */
139 3
        $builder = $this->scheduler->createTask($systemTask['handler_class'], $systemTask['workload']);
140 3
        $builder->setSystemKey($systemKey);
141 3
        if ($systemTask['cron_expression']) {
142 3
            $builder->cron($systemTask['cron_expression']);
143 3
        }
144
145 3
        $builder->schedule();
146
147 3
        $output->writeln(sprintf(' * System-task "%s" was <info>created</info>', $systemKey));
148 3
    }
149
150
    /**
151
     * Disable task identified by system-key.
152
     *
153
     * @param string $systemKey
154
     *
155
     * @return bool
156
     */
157 1
    private function disableSystemTask($systemKey)
158
    {
159 1
        if (!$task = $this->taskRepository->findBySystemKey($systemKey)) {
160
            return false;
161
        }
162
163 1
        $this->disableTask($task);
164
165 1
        return true;
166
    }
167
168
    /**
169
     * Disable given task identified.
170
     *
171
     * @param TaskInterface $task
172
     *
173
     * @return bool
174
     */
175 2
    public function disableTask(TaskInterface $task)
176
    {
177 2
        $task->setInterval($task->getInterval(), $task->getFirstExecution(), new \DateTime());
178
179 2
        return $this->abortPending($task);
180
    }
181
182
    /**
183
     * Update given task.
184
     *
185
     * @param string $systemKey
186
     * @param array $systemTask
187
     * @param TaskInterface $task
188
     */
189 2
    private function updateTask($systemKey, array $systemTask, TaskInterface $task)
190
    {
191 2
        if ($task->getHandlerClass() !== $systemTask['handler_class']
192 2
            || $task->getWorkload() !== $systemTask['workload']
193 2
        ) {
194 1
            throw new \InvalidArgumentException(
195 1
                sprintf('No update of handle-class or workload is supported for system-task "%s".', $systemKey)
196 1
            );
197
        }
198
199 1
        if ($task->getInterval() === $systemTask['cron_expression']) {
200
            return;
201
        }
202
203 1
        $task->setInterval(CronExpression::factory($systemTask['cron_expression']), $task->getFirstExecution());
204
205 1
        $this->abortPending($task);
206 1
        $this->scheduler->scheduleTasks();
207 1
    }
208
209
    /**
210
     * Abort pending execution for given task.
211
     *
212
     * @param TaskInterface $task
213
     *
214
     * @return bool
215
     */
216 3
    private function abortPending(TaskInterface $task)
217
    {
218 3
        if (!$execution = $this->taskExecutionRepository->findPending($task)) {
219
            return false;
220
        }
221
222 3
        $execution->setStatus(TaskStatus::ABORTED);
223 3
        $this->taskExecutionRepository->save($execution);
224
225 3
        return true;
226
    }
227
}
228