1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the Exchange Rate Bundle, an RunOpenCode project. |
4
|
|
|
* |
5
|
|
|
* (c) 2017 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\Event\FetchEvents; |
13
|
|
|
use RunOpenCode\Bundle\ExchangeRate\Exception\InvalidArgumentException; |
14
|
|
|
use RunOpenCode\ExchangeRate\Contract\ManagerInterface; |
15
|
|
|
use RunOpenCode\ExchangeRate\Contract\RateInterface; |
16
|
|
|
use RunOpenCode\ExchangeRate\Contract\SourceInterface; |
17
|
|
|
use RunOpenCode\ExchangeRate\Contract\SourcesRegistryInterface; |
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\EventDispatcher\EventDispatcherInterface; |
24
|
|
|
use Symfony\Component\EventDispatcher\GenericEvent; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Class FetchCommand |
28
|
|
|
* |
29
|
|
|
* Fetch rates from sources. |
30
|
|
|
* |
31
|
|
|
* @package RunOpenCode\Bundle\ExchangeRate\Command |
32
|
|
|
*/ |
33
|
|
|
class FetchCommand extends Command |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* @var EventDispatcherInterface |
37
|
|
|
*/ |
38
|
|
|
protected $eventDispatcher; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var ManagerInterface |
42
|
|
|
*/ |
43
|
|
|
protected $manager; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var SourcesRegistryInterface |
47
|
|
|
*/ |
48
|
|
|
protected $sourcesRegistry; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var SymfonyStyle |
52
|
|
|
*/ |
53
|
|
|
protected $output; |
54
|
|
|
|
55
|
|
|
public function __construct(EventDispatcherInterface $eventDispatcher, ManagerInterface $manager, SourcesRegistryInterface $sourcesRegistry) |
56
|
|
|
{ |
57
|
|
|
parent::__construct(); |
58
|
|
|
$this->eventDispatcher = $eventDispatcher; |
59
|
|
|
$this->manager = $manager; |
60
|
|
|
$this->sourcesRegistry = $sourcesRegistry; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* {@inheritdoc} |
65
|
|
|
*/ |
66
|
|
|
protected function configure() |
67
|
|
|
{ |
68
|
|
|
$this |
69
|
|
|
->setName('runopencode:exchange-rate:fetch') |
70
|
|
|
->setAliases([ |
71
|
|
|
'roc:exchange-rate:fetch' |
72
|
|
|
]) |
73
|
|
|
->setDescription('Fetch exchange rates from sources.') |
74
|
|
|
->addOption('date', 'd', InputOption::VALUE_OPTIONAL, 'State on which date exchange rates should be fetched.') |
75
|
|
|
->addOption('source', 'src', InputOption::VALUE_OPTIONAL, 'State which sources should be contacted only, separated with comma.') |
76
|
|
|
->addOption('silent', null, InputOption::VALUE_OPTIONAL, 'In silent mode, rates are fetched, but no event will be fired.', false) |
77
|
|
|
; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* {@inheritdoc} |
82
|
|
|
*/ |
83
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
84
|
|
|
{ |
85
|
|
|
$this->output = new SymfonyStyle($input, $output); |
86
|
|
|
|
87
|
|
|
$date = (null !== $input->getOption('date')) ? $this->sanitizeDate($input->getOption('date')) : new \DateTime('now'); |
88
|
|
|
$sources = $this->sanitizeSources($input->getOption('source')); |
89
|
|
|
|
90
|
|
|
$this->output->title(sprintf('Fetching rates for sources "%s" on "%s".', implode('", "', $sources), $date->format('Y-m-d'))); |
91
|
|
|
|
92
|
|
|
$errors = false; |
93
|
|
|
|
94
|
|
|
foreach ($sources as $source) { |
95
|
|
|
|
96
|
|
|
try { |
97
|
|
|
$rates = $this->manager->fetch($source, $date); |
98
|
|
|
|
99
|
|
|
$rows = array_map(function(RateInterface $rate) { |
100
|
|
|
return [ |
101
|
|
|
$rate->getCurrencyCode(), |
102
|
|
|
$rate->getRateType(), |
103
|
|
|
$rate->getValue(), |
104
|
|
|
]; |
105
|
|
|
}, $rates); |
106
|
|
|
|
107
|
|
|
$this->output->section(sprintf('Fetched rates for source: "%s"', $source)); |
108
|
|
|
$this->output->table(['Currency code', 'Rate type', 'Value'], $rows); |
109
|
|
|
|
110
|
|
View Code Duplication |
if (!$input->getOption('silent')) { |
|
|
|
|
111
|
|
|
$this->eventDispatcher->dispatch(FetchEvents::SUCCESS, new GenericEvent($sources, ['rates' => $rates])); |
112
|
|
|
} |
113
|
|
|
} catch (\Exception $e) { |
114
|
|
|
$this->output->error(sprintf('Could not fetch rates from source "%s".', $source)); |
115
|
|
|
$errors = true; |
116
|
|
|
|
117
|
|
View Code Duplication |
if (!$input->getOption('silent')) { |
|
|
|
|
118
|
|
|
$this->eventDispatcher->dispatch(FetchEvents::ERROR, new GenericEvent($sources, ['exception' => $e])); |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
if ($errors) { |
124
|
|
|
$this->output->error('Could not fetch all rates.'); |
125
|
|
|
return -1; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$this->output->success('Rates successfully fetched.'); |
129
|
|
|
return 0; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Sanitizes a date from console input. |
134
|
|
|
* |
135
|
|
|
* @param string|\DateTime $dateString A date |
136
|
|
|
* |
137
|
|
|
* @return \DateTime |
138
|
|
|
* |
139
|
|
|
* @throws InvalidArgumentException |
140
|
|
|
*/ |
141
|
|
|
protected function sanitizeDate($dateString) |
142
|
|
|
{ |
143
|
|
|
if ($dateString instanceof \DateTime) { |
144
|
|
|
return $dateString; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$date = \DateTime::createFromFormat('Y-m-d', $dateString); |
148
|
|
|
|
149
|
|
|
if ($date instanceof \DateTime) { |
150
|
|
|
return $date; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$date = new \DateTime($dateString); |
154
|
|
|
|
155
|
|
|
if ($date instanceof \DateTime) { |
156
|
|
|
return $date; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
throw new InvalidArgumentException(sprintf('Provided date "%s" is provided in unknown format. You should use "Y-m-d" instead.', $dateString)); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Clean sources from console input. |
164
|
|
|
* |
165
|
|
|
* @param mixed $sourcesString A sources. |
166
|
|
|
* |
167
|
|
|
* @return array|null |
168
|
|
|
* |
169
|
|
|
* @throws InvalidArgumentException |
170
|
|
|
*/ |
171
|
|
|
protected function sanitizeSources($sourcesString) |
172
|
|
|
{ |
173
|
|
|
$sources = $sourcesString; |
174
|
|
|
|
175
|
|
|
if (is_string($sources)) { |
176
|
|
|
$sources = array_map('trim', explode(',', $sources)); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
if (null === $sources || (is_array($sources) && count($sources) === 0)) { |
180
|
|
|
|
181
|
|
|
return array_map(function(SourceInterface $source) { |
182
|
|
|
return $source->getName(); |
183
|
|
|
}, $this->sourcesRegistry->all()); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if (!is_array($sources) && !$sources instanceof \Traversable) { |
187
|
|
|
throw new InvalidArgumentException(sprintf('Expected collection of sources as string, \Traversable or array, got "%s".', is_object($sourcesString) ? get_class($sourcesString) : gettype($sourcesString))); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
foreach ($sources as $source) { |
191
|
|
|
if (!$this->sourcesRegistry->has($source)) { |
192
|
|
|
throw new InvalidArgumentException(sprintf('Unknown source "%s" provided.', $source)); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $sources; |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.