Completed
Push — master ( c612e7...f3e1c8 )
by Christian
06:23
created

Task::perform()   B

Complexity

Conditions 5
Paths 27

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 43
rs 8.439
cc 5
eloc 28
nc 27
nop 1
1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[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
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core
18
 * @filesource
19
 */
20
21
namespace Tenside\Core\Task;
22
23
use Composer\IO\ConsoleIO;
24
use Composer\IO\IOInterface;
25
use Symfony\Component\Console\Helper\HelperSet;
26
use Symfony\Component\Console\Input\ArrayInput;
27
use Symfony\Component\Console\Input\InputInterface;
28
use Tenside\Core\Util\JsonArray;
29
30
/**
31
 * Abstract base class for tasks.
32
 */
33
abstract class Task
1 ignored issue
show
Coding Style introduced by
Task does not seem to conform to the naming convention (^Abstract|Factory$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
34
{
35
    /**
36
     * The type of the task.
37
     */
38
    const SETTING_TYPE = 'type';
39
40
    /**
41
     * The id of the task.
42
     */
43
    const SETTING_ID = 'id';
44
45
    /**
46
     * This state determines that the task is still awaiting to be executed.
47
     */
48
    const STATE_PENDING = 'PENDING';
49
50
    /**
51
     * This state determines that the task is still running.
52
     */
53
    const STATE_RUNNING = 'RUNNING';
54
55
    /**
56
     * This state determines that the task has been finished.
57
     */
58
    const STATE_FINISHED = 'FINISHED';
59
60
    /**
61
     * This state determines that the task has been finished with errors.
62
     */
63
    const STATE_ERROR = 'ERROR';
64
65
    /**
66
     * The task file to write to.
67
     *
68
     * @var JsonArray
69
     */
70
    protected $file;
71
72
    /**
73
     * The log file to write to.
74
     *
75
     * @var string
76
     */
77
    protected $logFile;
78
79
    /**
80
     * The input/output handler.
81
     *
82
     * @var IOInterface
83
     */
84
    private $inputOutput;
85
86
    /**
87
     * Task constructor.
88
     *
89
     * @param JsonArray $file The json file to write to.
90
     */
91
    public function __construct(JsonArray $file)
92
    {
93
        $this->file = $file;
94
95
        if ($this->file->has('log')) {
96
            $this->logFile = $this->file->get('log');
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->file->get('log') can also be of type array or integer. However, the property $logFile is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
97
        }
98
    }
99
100
    /**
101
     * Retrieve the task id.
102
     *
103
     * @return string
1 ignored issue
show
Documentation introduced by
Should the return type not be array|string|integer|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
104
     */
105
    public function getId()
106
    {
107
        return $this->file->get('id');
108
    }
109
110
    /**
111
     * Retrieve the current output.
112
     *
113
     * @param null|int $offset The offset in bytes to read from.
114
     *
115
     * @return string
116
     */
117
    public function getOutput($offset = null)
118
    {
119
        if (!$this->logFile) {
120
            return '';
121
        }
122
123
        return (string) file_get_contents($this->logFile, FILE_BINARY, null, $offset);
124
    }
125
126
    /**
127
     * Retrieve the task type name.
128
     *
129
     * @return string
130
     */
131
    abstract public function getType();
132
133
    /**
134
     * Perform the task.
135
     *
136
     * @param string $logFile The log file to write to.
137
     *
138
     * @return void
139
     *
140
     * @throws \LogicException   When the task has already been run.
141
     *
142
     * @throws \RuntimeException When the execution. failed.
143
     */
144
    public function perform($logFile)
145
    {
146
        if (self::STATE_PENDING !== $this->getStatus()) {
147
            throw new \LogicException('Attempted to run task ' . $this->getId() . ' twice.');
148
        }
149
150
        try {
151
            if (!is_dir(dirname($logFile))) {
152
                mkdir(dirname($logFile), 0777, true);
153
            }
154
155
            file_put_contents($logFile, '', FILE_BINARY);
156
157
            $this->logFile = $logFile;
158
            $this->file->set('log', $logFile);
159
160
            $this->setStatus(self::STATE_RUNNING);
161
            $this->addOutput('Task ' . $this->getId() .  ' started.' . "\n");
162
163
            $this->doPerform();
164
        } catch (\Exception $exception) {
165
            $this->addOutput('--------------------------------------------------------' . "\n");
166
            $this->addOutput('Exception occured: ' . $exception->getMessage() . "\n");
167
            $this->addOutput($exception->getTraceAsString() . "\n");
168
            $loopException = $exception;
169
            while ($loopException = $loopException->getPrevious()) {
170
                $this->addOutput('Chained exception: ' . $loopException->getMessage() . "\n");
171
                $this->addOutput($loopException->getTraceAsString() . "\n");
172
            }
173
            $this->addOutput('--------------------------------------------------------' . "\n");
174
175
            $this->setStatus(self::STATE_ERROR);
176
177
            throw new \RuntimeException(
178
                'Task ' . $this->getId() . ' errored: ' . $exception->getMessage(),
179
                1,
180
                $exception
181
            );
182
        }
183
184
        $this->addOutput('Finished without error.' . "\n");
185
        $this->setStatus(self::STATE_FINISHED);
186
    }
187
188
    /**
189
     * Perform the task.
190
     *
191
     * @return void
192
     */
193
    abstract public function doPerform();
194
195
    /**
196
     * Add some output.
197
     *
198
     * @param string $string The output string to append to the output.
199
     *
200
     * @return void
201
     *
202
     * @throws \LogicException When called prior to perform().
203
     */
204
    public function addOutput($string)
205
    {
206
        if (!$this->logFile) {
207
            throw new \LogicException('The has not started to run yet.');
208
        }
209
210
        file_put_contents($this->logFile, $string, (FILE_APPEND | FILE_BINARY));
211
    }
212
213
    /**
214
     * Retrieve the IO interface.
215
     *
216
     * @return IOInterface
217
     */
218
    public function getIO()
219
    {
220
        if (!isset($this->inputOutput)) {
221
            $this->inputOutput = new ConsoleIO($this->getInput(), new TaskOutput($this), new HelperSet([]));
222
        }
223
224
        return $this->inputOutput;
225
    }
226
227
    /**
228
     * Retrieve the current status of a task.
229
     *
230
     * @return string
1 ignored issue
show
Documentation introduced by
Should the return type not be array|string|integer|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
231
     */
232
    public function getStatus()
233
    {
234
        return $this->file->get('status');
235
    }
236
237
    /**
238
     * Remove the attached files for this task from disk.
239
     *
240
     * @return void
241
     */
242
    public function removeAssets()
243
    {
244
        // Base implementation does only know about log file.
245
        if ($this->logFile) {
246
            if (file_exists($this->logFile)) {
247
                unlink($this->logFile);
248
            }
249
            $this->logFile = null;
250
            $this->file->remove('log');
251
        }
252
    }
253
254
    /**
255
     * Set the task state.
256
     *
257
     * @param string $status The status code.
258
     *
259
     * @return void
260
     */
261
    protected function setStatus($status)
262
    {
263
        $this->file->set('status', $status);
264
    }
265
266
    /**
267
     * Retrieve the Input handler.
268
     *
269
     * @return InputInterface
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use ArrayInput.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
270
     */
271
    private function getInput()
272
    {
273
        $input = new ArrayInput([]);
274
275
        $input->setInteractive(false);
276
277
        return $input;
278
    }
279
}
280