Completed
Push — master ( c8fe9c...8ecce1 )
by Nikola
02:48
created

FetchCommand::execute()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 52
ccs 0
cts 33
cp 0
rs 8.6868
cc 5
eloc 29
nc 5
nop 2
crap 30

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of the Exchange Rate Bundle, an RunOpenCode project.
4
 *
5
 * (c) 2016 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\Bundle\ExchangeRate\Command;
11
12
use RunOpenCode\Bundle\ExchangeRate\Contract\NotificationInterface;
13
use RunOpenCode\ExchangeRate\Contract\ManagerInterface;
14
use RunOpenCode\ExchangeRate\Contract\RateInterface;
15
use RunOpenCode\ExchangeRate\Contract\SourceInterface;
16
use RunOpenCode\ExchangeRate\Contract\SourcesRegistryInterface;
17
use RunOpenCode\ExchangeRate\Log\LoggerAwareTrait;
18
use Symfony\Component\Console\Command\Command;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Console\Style\SymfonyStyle;
23
use Symfony\Component\Console\Style\OutputStyle;
24
25
/**
26
 * Class FetchCommand
27
 *
28
 * Fetch rates from sources.
29
 *
30
 * @package RunOpenCode\Bundle\ExchangeRate\Command
31
 */
32
class FetchCommand extends Command
33
{
34
    use LoggerAwareTrait;
35
36
    /**
37
     * @var ManagerInterface
38
     */
39
    protected $manager;
40
41
    /**
42
     * @var SourcesRegistryInterface
43
     */
44
    protected $sourcesRegistry;
45
46
    /**
47
     * @var SymfonyStyle
48
     */
49
    protected $outputStyle;
50
51
    /**
52
     * @var NotificationInterface[]
53
     */
54
    protected $successNotifications;
55
56
    /**
57
     * @var NotificationInterface[]
58
     */
59
    protected $errorNotifications;
60
61
    public function __construct(ManagerInterface $manager, SourcesRegistryInterface $sourcesRegistry)
62
    {
63
        parent::__construct();
64
        $this->manager = $manager;
65
        $this->sourcesRegistry = $sourcesRegistry;
66
        $this->successNotifications = [];
67
        $this->errorNotifications = [];
68
    }
69
70
    /**
71
     * Add success notification to command notifications stack.
72
     *
73
     * @param NotificationInterface $notification Success notification to add.
74
     * @return FetchCommand $this Fluent interface.
75
     */
76
    public function addSuccessNotification(NotificationInterface $notification)
77
    {
78
        $this->successNotifications[] = $notification;
79
        return $this;
80
    }
81
82
    /**
83
     * Add error notification to command notifications stack.
84
     *
85
     * @param NotificationInterface $notification Error notification to add.
86
     * @return FetchCommand $this Fluent interface.
87
     */
88
    public function addErrorNotification(NotificationInterface $notification)
89
    {
90
        $this->errorNotifications[] = $notification;
91
        return $this;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    protected function configure()
98
    {
99
        $this
100
            ->setName('roc:exchange-rate:fetch')
101
            ->setDescription('Fetch exchange rates from sources.')
102
            ->addOption('date', 'd', InputOption::VALUE_OPTIONAL, 'State on which date exchange rates should be fetched.')
103
            ->addOption('source', 'src', InputOption::VALUE_OPTIONAL, 'State which sources should be contacted only, separated with comma.')
104
            ->addOption('silent', null, InputOption::VALUE_OPTIONAL, 'In silent mode, rates are fetched, but no notification is being fired on any event.', false)
105
        ;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    protected function execute(InputInterface $input, OutputInterface $output)
112
    {
113
        $outputStyle = new SymfonyStyle($input, $output);
114
115
        try {
116
            $this
117
                ->cleanInputDate($input, $outputStyle)
118
                ->cleanInputSources($input, $outputStyle);
119
        } catch (\Exception $e) {
120
121
            $this->getLogger()->critical('Unable to execute command. Reason: "{message}".', array(
122
                'message' => $e->getMessage(),
123
                'exception' => $e
124
            ));
125
126
            return;
127
        }
128
129
        $this->displayCommandBegin($input, $outputStyle);
130
131
        try {
132
133
            $rates = $this->doFetch($input);
134
135
        } catch (\Exception $e) {
136
137
            $this->displayCommandError($outputStyle);
138
139
            if (!$input->getOption('silent')) {
140
                $this->dispatchErrorNotifications($input->getOption('source'), $input->getOption('date'));
141
            }
142
143
            $this->getLogger()->critical('Unable to fetch rates. Reason: "{message}".', array(
144
                'message' => $e->getMessage(),
145
                'exception' => $e
146
            ));
147
148
            return;
149
        }
150
151
        $this->displayCommandSuccess($outputStyle);
152
153
        if (!$input->getOption('silent')) {
154
            $this->dispatchSuccessNotifications($input->getOption('source'), $input->getOption('date'), $rates);
155
        }
156
157
        $this->getLogger()->info('Successfully fetched rates "{rates}".', array(
158
            'rates' => implode(', ', array_map(function(RateInterface $rate) {
159
                return sprintf('%s => %s', $rate->getBaseCurrencyCode(), $rate->getCurrencyCode());
160
            }, $rates))
161
        ));
162
    }
163
164
    /**
165
     * Clean date from console input.
166
     *
167
     * @param InputInterface $input Console input.
168
     * @param OutputStyle $outputStyle Output style to use.
169
     * @return FetchCommand $this Fluent interface.
170
     *
171
     * @throws \Exception
172
     */
173
    protected function cleanInputDate(InputInterface $input, OutputStyle $outputStyle)
174
    {
175
        $date = $input->getOption('date');
176
177
        if (!empty($date)) {
178
            $date = \DateTime::createFromFormat('Y-m-d', $date);
179
180
            if ($date === false) {
181
                $outputStyle->error('Invalid date format provided, expected format is "Y-m-d".');
182
                throw new \Exception;
183
            }
184
        } else {
185
            $date = new \DateTime('now');
186
        }
187
188
        $input->setOption('date', $date);
0 ignored issues
show
Documentation introduced by
$date is of type object<DateTime>, but the function expects a string|boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
189
190
        return $this;
191
    }
192
193
    /**
194
     * Clean sources from console input.
195
     *
196
     * @param InputInterface $input Console input.
197
     * @param OutputStyle $outputStyle Output style to use.
198
     * @return FetchCommand $this Fluent interface.
199
     *
200
     * @throws \Exception
201
     */
202
    protected function cleanInputSources(InputInterface $input, OutputStyle $outputStyle)
203
    {
204
        $sources = $input->getOption('source');
205
206
        if (!empty($sources)) {
207
            $sources = array_map('trim', explode(',', $sources));
208
209
            foreach ($sources as $source) {
210
211
                if (!$this->sourcesRegistry->has($source)) {
212
213
                    $outputStyle->error(sprintf('Invalid source name "%s" provided, available sources are "%s".', $source, implode(', ', array_map(function(SourceInterface $source) {
214
                        return $source->getName();
215
                    }, $this->sourcesRegistry->all()))));
216
217
                    throw new \Exception;
218
                }
219
            }
220
        }
221
222
        $input->setOption('source', $sources);
223
224
        return $this;
225
    }
226
227
    /**
228
     * Display command begin note.
229
     *
230
     * @param InputInterface $input Console input.
231
     * @param OutputStyle $outputStyle Console style.
232
     * @return FetchCommand $this Fluent interface.
233
     */
234
    protected function displayCommandBegin(InputInterface $input, OutputStyle $outputStyle)
235
    {
236
        $outputStyle->title('Exchange rates:');
237
        $outputStyle->text(
238
            sprintf(
239
                'Fetching from %s for date %s....',
240
                ($input->getOption('source') ? sprintf('"%s"', implode('", "', $input->getOption('source'))) : 'all sources'),
241
                $input->getOption('date')->format('Y-m-d')
242
            )
243
        );
244
245
        return $this;
246
    }
247
248
    /**
249
     * Do fetch rates.
250
     *
251
     * @param InputInterface $input Console input.
252
     * @return RateInterface[] Fetched rates.
253
     * @throws \Exception
254
     */
255
    protected function doFetch(InputInterface $input)
256
    {
257
        try {
258
259
            $rates = $this->manager->fetch($input->getOption('source'), $input->getOption('date'));
260
261
            $this->getLogger()->info(sprintf('Rates fetched from %s for date %s.', $input->getOption('source') ? sprintf('"%s"', implode('", "', $input->getOption('source'))) : 'all sources', $input->getOption('date')->format('Y-m-d')));
262
263
        } catch (\Exception $e) {
264
265
            $this->getLogger()->critical('Unable to fetch rates.', array(
266
                'date' => $input->getOption('date')->format('Y-m-d'),
267
                'sources' => $input->getOption('source') ? sprintf('"%s"', implode('", "', $input->getOption('source'))) : 'All sources',
268
                'exception' => array(
269
                    'message' => $e->getMessage(),
270
                    'code' => $e->getCode(),
271
                    'file' => $e->getFile(),
272
                    'line' => $e->getLine(),
273
                    'trace' => $e->getTraceAsString()
274
                )
275
            ));
276
277
            throw $e;
278
        }
279
280
        return $rates;
281
    }
282
283
    /**
284
     * Display command success note.
285
     *
286
     * @param OutputStyle $outputStyle
287
     * @return FetchCommand $this Fluent interface.
288
     */
289
    protected function displayCommandSuccess(OutputStyle $outputStyle)
290
    {
291
        $outputStyle->success('Exchange rates successfully fetched.');
292
        return $this;
293
    }
294
295
    /**
296
     * Display command error note.
297
     *
298
     * @param OutputStyle $outputStyle
299
     * @return FetchCommand $this Fluent interface.
300
     */
301
    protected function displayCommandError(OutputStyle $outputStyle)
302
    {
303
        $outputStyle->error('Unable to fetch data from source(s). See log for details.');
304
        return $this;
305
    }
306
307
    /**
308
     * Dispatch success notifications.
309
     *
310
     * @param null|array $source Sources for which command is executed.
311
     * @param \DateTime $date Date for which rates are fetched.
312
     * @param RateInterface[] $rates Fetched rates
313
     * @return FetchCommand $this Fluent interface.
314
     */
315
    protected function dispatchSuccessNotifications($source, \DateTime $date, array $rates)
316
    {
317
        foreach ($this->successNotifications as $notification) {
318
319
            $notification->notify(array(
320
                'source' => $source,
321
                'date' => $date,
322
                'rates' => $rates
323
            ));
324
        }
325
326
        return $this;
327
    }
328
329
    /**
330
     * Dispatch error notifications.
331
     *
332
     * @param null|array $source Sources for which command is executed.
333
     * @param \DateTime $date Date for which rates are fetched.
334
     * @return FetchCommand $this Fluent interface.
335
     */
336
    protected function dispatchErrorNotifications($source, \DateTime $date)
337
    {
338
        foreach ($this->errorNotifications as $notification) {
339
340
            $notification->notify(array(
341
                'source' => $source,
342
                'date' => $date
343
            ));
344
        }
345
346
        return $this;
347
    }
348
}
349