Skip to content

Commit d87fdb6

Browse files
authored
Ensure processors are determined at runtime (#35)
1 parent 3d86660 commit d87fdb6

File tree

7 files changed

+362
-28
lines changed

7 files changed

+362
-28
lines changed

phpunit.xml

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
<?xml version="1.0"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" verbose="false">
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
5+
colors="true"
6+
>
37
<coverage processUncoveredFiles="true">
48
<include>
59
<directory suffix=".php">src</directory>
610
</include>
711
</coverage>
812
<testsuites>
913
<testsuite name="all">
10-
<directory suffix="Test.php" phpVersion="7.2" phpVersionOperator="&gt;=">test</directory>
14+
<directory phpVersion="7.4" phpVersionOperator=">=">test</directory>
1115
</testsuite>
1216
</testsuites>
1317
</phpunit>

src/FileFetcher.php

+167-24
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,59 @@
1717
class FileFetcher extends AbstractPersistentJob
1818
{
1919

20+
/**
21+
* Array of processor class names provided by the configuration.
22+
*
23+
* @var string[]
24+
*/
2025
protected array $customProcessorClasses = [];
2126

27+
/**
28+
* The processor this file fetcher will use.
29+
*
30+
* Stored here so that we don't have to recompute it.
31+
*
32+
* @var \FileFetcher\Processor\ProcessorInterface|null
33+
*/
34+
private ?ProcessorInterface $processor = null;
35+
36+
/**
37+
* {@inheritDoc}
38+
*
39+
* We override ::get() because it can set values for both newly constructed
40+
* objects and re-hydrated ones.
41+
*/
42+
public static function get(string $identifier, $storage, array $config = null)
43+
{
44+
$ff = parent::get($identifier, $storage, $config);
45+
// If we see that a processor is configured, we need to handle some
46+
// special cases. It might be that the hydrated values for
47+
// $customProcessorClasses are the same, but the caller could also be
48+
// telling us to use a different processor than any that were hydrated
49+
// from storage. We keep the existing ones and prepend the new ones.
50+
$ff->addProcessors($config);
51+
$storage->store(json_encode($ff), $identifier);
52+
return $ff;
53+
}
54+
2255
/**
2356
* Constructor.
57+
*
58+
* Constructor is protected. Use static::get() to instantiate a file
59+
* fetcher.
60+
*
61+
* @param string $identifier
62+
* File fetcher job identifier.
63+
* @param $storage
64+
* File fetcher job storage object.
65+
* @param array|NULL $config
66+
* Configuration for the file fetcher.
67+
*
68+
* @see static::get()
2469
*/
2570
protected function __construct(string $identifier, $storage, array $config = null)
2671
{
27-
parent::__construct($identifier, $storage, $config);
72+
parent::__construct($identifier, $storage);
2873

2974
$this->setProcessors($config);
3075

@@ -42,15 +87,6 @@ protected function __construct(string $identifier, $storage, array $config = nul
4287
'temporary_directory' => $config['temporaryDirectory'],
4388
];
4489

45-
// [State]
46-
47-
foreach ($this->getProcessors() as $processor) {
48-
if ($processor->isServerCompatible($state)) {
49-
$state['processor'] = get_class($processor);
50-
break;
51-
}
52-
}
53-
5490
$this->getResult()->setData(json_encode($state));
5591
}
5692

@@ -62,6 +98,9 @@ public function setTimeLimit(int $seconds): bool
6298
return parent::setTimeLimit($seconds);
6399
}
64100

101+
/**
102+
* {@inheritDoc}
103+
*/
65104
protected function runIt()
66105
{
67106
$state = $this->getProcessor()->setupState($this->getState());
@@ -71,10 +110,19 @@ protected function runIt()
71110
return $info['result'];
72111
}
73112

113+
/**
114+
* Gets the combined custom and default processors list.
115+
*
116+
* @return array
117+
* The combined custom and default processors list, prioritizing the
118+
* custom ones in the order they were defined.
119+
*/
74120
protected function getProcessors(): array
75121
{
76122
$processors = self::getDefaultProcessors();
77-
foreach ($this->customProcessorClasses as $processorClass) {
123+
// Reverse the array so when we merge it all back together it's in the
124+
// correct order of precedent.
125+
foreach (array_reverse($this->customProcessorClasses) as $processorClass) {
78126
if ($processor = $this->getCustomProcessorInstance($processorClass)) {
79127
$processors = array_merge([get_class($processor) => $processor], $processors);
80128
}
@@ -84,15 +132,32 @@ protected function getProcessors(): array
84132

85133
private static function getDefaultProcessors()
86134
{
87-
$processors = [];
88-
$processors[Local::class] = new Local();
89-
$processors[Remote::class] = new Remote();
90-
return $processors;
135+
return [
136+
Local::class => new Local(),
137+
Remote::class => new Remote(),
138+
];
91139
}
92140

93-
protected function getProcessor(): ProcessorInterface
141+
/**
142+
* Get the processor used by this file fetcher object.
143+
*
144+
* @return \FileFetcher\Processor\ProcessorInterface|null
145+
* A processor object, determined by configuration, or NULL if none is
146+
* suitable.
147+
*/
148+
protected function getProcessor(): ?ProcessorInterface
94149
{
95-
return $this->getProcessors()[$this->getStateProperty('processor')];
150+
if ($this->processor) {
151+
return $this->processor;
152+
}
153+
$state = $this->getState();
154+
foreach ($this->getProcessors() as $processor) {
155+
if ($processor->isServerCompatible($state)) {
156+
$this->processor = $processor;
157+
break;
158+
}
159+
}
160+
return $this->processor;
96161
}
97162

98163
private function validateConfig($config): array
@@ -109,30 +174,108 @@ private function validateConfig($config): array
109174
return $config;
110175
}
111176

177+
/**
178+
* Set custom processors for this file fetcher object.
179+
*
180+
* @param $config
181+
* Configuration array, as passed to __construct() or Job::get(). Should
182+
* contain an array of processor class names under the key 'processors'.
183+
*
184+
* @see self::addProcessors()
185+
*/
112186
protected function setProcessors($config)
113187
{
114-
if (!isset($config['processors'])) {
115-
return;
188+
$this->processor = null;
189+
$processors = $config['processors'] ?? [];
190+
if (!is_array($processors)) {
191+
$processors = [];
116192
}
193+
$this->customProcessorClasses = $processors;
194+
}
117195

118-
if (!is_array($config['processors'])) {
196+
/**
197+
* Add configured processors to the ones already set in the object.
198+
*
199+
* Existing custom processor classes will be preserved, but any present in
200+
* the new config will be prioritized.
201+
*
202+
* @param array $config
203+
* Configuration array, as passed to __construct() or Job::get(). Should
204+
* contain an array of processor class names under the key 'processors'.
205+
*
206+
* @see self::setProcessors()
207+
*/
208+
protected function addProcessors(array $config): void
209+
{
210+
// If we have config, and we don't have custom classes already, do the
211+
// easy thing.
212+
if (($config['processors'] ?? false) && empty($this->customProcessorClasses)) {
213+
$this->setProcessors($config);
119214
return;
120215
}
216+
if ($config_processors = $config['processors'] ?? false) {
217+
$this->processor = null;
218+
// Unset the configured processors from customProcessorClasses.
219+
$this->unsetDuplicateCustomProcessorClasses($config_processors);
220+
// Merge in the configuration.
221+
$this->customProcessorClasses = array_merge(
222+
$config_processors,
223+
$this->customProcessorClasses
224+
);
225+
}
226+
}
121227

122-
$this->customProcessorClasses = $config['processors'];
228+
/**
229+
* Unset items in $this->customProcessorClasses present in the given array.
230+
*
231+
* @param array $processors
232+
* Processor class names to be removed from the list of custom processor
233+
* classes.
234+
*/
235+
private function unsetDuplicateCustomProcessorClasses(array $processors):void
236+
{
237+
foreach ($processors as $processor) {
238+
// Use array_keys() with its search parameter.
239+
foreach (array_keys($this->customProcessorClasses, $processor) as $existing) {
240+
unset($this->customProcessorClasses[$existing]);
241+
}
242+
}
123243
}
124244

125-
private function getCustomProcessorInstance($processorClass)
245+
/**
246+
* Get an instance of the given custom processor class.
247+
*
248+
* @param $processorClass
249+
* Processor class name.
250+
*
251+
* @return \FileFetcher\Processor\ProcessorInterface|null
252+
* An instance of the processor class. If the given class name does not
253+
* exist, or does not implement ProcessorInterface, then null is
254+
* returned.
255+
*/
256+
private function getCustomProcessorInstance($processorClass): ?ProcessorInterface
126257
{
127258
if (!class_exists($processorClass)) {
128-
return;
259+
return null;
129260
}
130261

131262
$classes = class_implements($processorClass);
132263
if (!in_array(ProcessorInterface::class, $classes)) {
133-
return;
264+
return null;
134265
}
135266

136267
return new $processorClass();
137268
}
269+
270+
/**
271+
* {@inheritDoc}
272+
*/
273+
protected function serializeIgnoreProperties(): array
274+
{
275+
// Tell our serializer to ignore processor information.
276+
return array_merge(
277+
parent::serializeIgnoreProperties(),
278+
['processor']
279+
);
280+
}
138281
}

src/Processor/Local.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Local extends ProcessorBase implements ProcessorInterface
88
{
99
public function isServerCompatible(array $state): bool
1010
{
11-
$path = $state['source'];
11+
$path = $state['source'] ?? '';
1212

1313
try {
1414
if (file_exists($path) && !is_dir($path)) {

src/Processor/Remote.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class Remote extends ProcessorBase implements ProcessorInterface
1313

1414
public function isServerCompatible(array $state): bool
1515
{
16-
return preg_match(self::HTTP_URL_REGEX, $state['source']) === 1;
16+
$source = $state['source'] ?? '';
17+
return preg_match(self::HTTP_URL_REGEX, $source) === 1;
1718
}
1819

1920
public function setupState(array $state): array

0 commit comments

Comments
 (0)