Completed
Pull Request — master (#533)
by Greg
03:36
created

TaskIO::write()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
namespace Robo\Common;
3
4
use Robo\Robo;
5
use Robo\TaskInfo;
6
use Consolidation\Log\ConsoleLogLevel;
7
use Psr\Log\LoggerAwareTrait;
8
use Psr\Log\LogLevel;
9
use Robo\Contract\ProgressIndicatorAwareInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
12
/**
13
 * Task input/output methods.  TaskIO is 'used' in BaseTask, so any
14
 * task that extends this class has access to all of the methods here.
15
 * printTaskInfo, printTaskSuccess, and printTaskError are the three
16
 * primary output methods that tasks are encouraged to use.  Tasks should
17
 * avoid using the IO trait output methods.
18
 */
19
trait TaskIO
20
{
21
    use LoggerAwareTrait;
22
    use ConfigAwareTrait;
23
    use OutputAwareTrait;
24
25
    protected $verbosityThreshold = 0;
26
27
    /**
28
     * Required verbocity level before any TaskIO output will be produced.
29
     * e.g. OutputInterface::VERBOSITY_VERBOSE
30
     */
31
    public function setVerbosityThreshold($verbosityThreshold)
32
    {
33
        $this->verbosityThreshold = $verbosityThreshold;
34
        return $this;
35
    }
36
37
    public function verbosityThreshold()
38
    {
39
        return $this->verbosityThreshold;
40
    }
41
42
    /**
43
     * @return mixed|null|\Psr\Log\LoggerInterface
44
     */
45
    public function logger()
46
    {
47
        // $this->logger should always be set in Robo core tasks.
48
        if ($this->logger) {
49
            return $this->logger;
50
        }
51
52
        // TODO: Remove call to Robo::logger() once maintaining backwards
53
        // compatibility with legacy external Robo tasks is no longer desired.
54
        if (!Robo::logger()) {
55
            return null;
56
        }
57
58
        static $gaveDeprecationWarning = false;
59
        if (!$gaveDeprecationWarning) {
60
            trigger_error('No logger set for ' . get_class($this) . '. Use $this->task(Foo::class) rather than new Foo() in loadTasks to ensure the builder can initialize task the task, or use $this->collectionBuilder()->taskFoo() if creating one task from within another.', E_USER_DEPRECATED);
61
            $gaveDeprecationWarning = true;
62
        }
63
        return Robo::logger();
64
    }
65
66
    /**
67
     * Print information about a task in progress.
68
     *
69
     * With the Symfony Console logger, NOTICE is displayed at VERBOSITY_VERBOSE
70
     * and INFO is displayed at VERBOSITY_VERY_VERBOSE.
71
     *
72
     * Robo overrides the default such that NOTICE is displayed at
73
     * VERBOSITY_NORMAL and INFO is displayed at VERBOSITY_VERBOSE.
74
     *
75
     * n.b. We should probably have printTaskNotice for our ordinary
76
     * output, and use printTaskInfo for less interesting messages.
77
     *
78
     * @param string $text
79
     * @param null|array $context
80
     */
81
    protected function printTaskInfo($text, $context = null)
82
    {
83
        // The 'note' style is used for both 'notice' and 'info' log levels;
84
        // However, 'notice' is printed at VERBOSITY_NORMAL, whereas 'info'
85
        // is only printed at VERBOSITY_VERBOSE.
86
        $this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
87
    }
88
89
    /**
90
     * Provide notification that some part of the task succeeded.
91
     *
92
     * With the Symfony Console logger, success messages are remapped to NOTICE,
93
     * and displayed in VERBOSITY_VERBOSE. When used with the Robo logger,
94
     * success messages are displayed at VERBOSITY_NORMAL.
95
     *
96
     * @param string $text
97
     * @param null|array $context
98
     */
99
    protected function printTaskSuccess($text, $context = null)
100
    {
101
        // Not all loggers will recognize ConsoleLogLevel::SUCCESS.
102
        // We therefore log as LogLevel::NOTICE, and apply a '_level'
103
        // override in the context so that this message will be
104
        // logged as SUCCESS if that log level is recognized.
105
        $context['_level'] = ConsoleLogLevel::SUCCESS;
106
        $this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
107
    }
108
109
    /**
110
     * Provide notification that there is something wrong, but
111
     * execution can continue.
112
     *
113
     * Warning messages are displayed at VERBOSITY_NORMAL.
114
     *
115
     * @param string $text
116
     * @param null|array $context
117
     */
118
    protected function printTaskWarning($text, $context = null)
119
    {
120
        $this->printTaskOutput(LogLevel::WARNING, $text, $this->getTaskContext($context));
121
    }
122
123
    /**
124
     * Provide notification that some operation in the task failed,
125
     * and the task cannot continue.
126
     *
127
     * Error messages are displayed at VERBOSITY_NORMAL.
128
     *
129
     * @param string $text
130
     * @param null|array $context
131
     */
132
    protected function printTaskError($text, $context = null)
133
    {
134
        $this->printTaskOutput(LogLevel::ERROR, $text, $this->getTaskContext($context));
135
    }
136
137
    /**
138
     * Provide debugging notification.  These messages are only
139
     * displayed if the log level is VERBOSITY_DEBUG.
140
     *
141
     * @param string$text
142
     * @param null|array $context
143
     */
144
    protected function printTaskDebug($text, $context = null)
145
    {
146
        $this->printTaskOutput(LogLevel::DEBUG, $text, $this->getTaskContext($context));
147
    }
148
149
    /**
150
     * @param string $level
151
     *   One of the \Psr\Log\LogLevel constant
152
     * @param string $text
153
     * @param null|array $context
154
     */
155
    protected function printTaskOutput($level, $text, $context)
156
    {
157
        if ($this->output()->getVerbosity() < $this->verbosityThreshold()) {
158
            return;
159
        }
160
        $logger = $this->logger();
161
        if (!$logger) {
162
            return;
163
        }
164
        // Hide the progress indicator, if it is visible.
165
        $inProgress = $this->hideTaskProgress();
166
        $logger->log($level, $text, $this->getTaskContext($context));
167
        // After we have printed our log message, redraw the progress indicator.
168
        $this->showTaskProgress($inProgress);
169
    }
170
171
    /**
172
     * Print a message if the selected verbosity level is over this task's
173
     * verbosity threshhold.
174
     */
175
    protected function write($message)
176
    {
177
        if ($this->output()->getVerbosity() < $this->verbosityThreshold()) {
178
            return;
179
        }
180
        $this->output()->write($message);
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    protected function hideTaskProgress()
187
    {
188
        $inProgress = false;
189
        if ($this instanceof ProgressIndicatorAwareInterface) {
190
            $inProgress = $this->inProgress();
191
        }
192
193
        // If a progress indicator is running on this task, then we mush
194
        // hide it before we print anything, or its display will be overwritten.
195
        if ($inProgress) {
196
            $inProgress = $this->hideProgressIndicator();
0 ignored issues
show
Bug introduced by
It seems like hideProgressIndicator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
197
        }
198
        return $inProgress;
199
    }
200
201
    /**
202
     * @param $inProgress
203
     */
204
    protected function showTaskProgress($inProgress)
205
    {
206
        if ($inProgress) {
207
            $this->restoreProgressIndicator($inProgress);
0 ignored issues
show
Bug introduced by
It seems like restoreProgressIndicator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
208
        }
209
    }
210
211
    /**
212
     * Format a quantity of bytes.
213
     *
214
     * @param int $size
215
     * @param int $precision
216
     *
217
     * @return string
218
     */
219
    protected function formatBytes($size, $precision = 2)
220
    {
221
        if ($size === 0) {
222
            return 0;
223
        }
224
        $base = log($size, 1024);
225
        $suffixes = array('', 'k', 'M', 'G', 'T');
226
        return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
227
    }
228
229
    /**
230
     * Get the formatted task name for use in task output.
231
     * This is placed in the task context under 'name', and
232
     * used as the log label by Robo\Common\RoboLogStyle,
233
     * which is inserted at the head of log messages by
234
     * Robo\Common\CustomLogStyle::formatMessage().
235
     *
236
     * @param null|object $task
237
     *
238
     * @return string
239
     */
240
    protected function getPrintedTaskName($task = null)
241
    {
242
        if (!$task) {
243
            $task = $this;
244
        }
245
        return TaskInfo::formatTaskName($task);
0 ignored issues
show
Bug introduced by
It seems like $task can also be of type this<Robo\Common\TaskIO>; however, Robo\TaskInfo::formatTaskName() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
246
    }
247
248
    /**
249
     * @param null|array $context
250
     *
251
     * @return array with context information
252
     */
253
    protected function getTaskContext($context = null)
254
    {
255
        if (!$context) {
256
            $context = [];
257
        }
258
        if (!is_array($context)) {
259
            $context = ['task' => $context];
260
        }
261
        if (!array_key_exists('task', $context)) {
262
            $context['task'] = $this;
263
        }
264
265
        return $context + TaskInfo::getTaskContext($context['task']);
266
    }
267
}
268