Completed
Push — 12.x ( ef08f3...057fdd )
by Tim
14s queued 10s
created

SimpleFileResolver   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 67.65%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 4
dl 0
loc 383
ccs 69
cts 102
cp 0.6765
rs 9.0399
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getRegex() 0 4 1
A countMatches() 0 4 1
A addMatch() 0 4 1
A getMatch() 0 6 2
A getPatternKeys() 0 14 1
A removeLineFromFile() 0 4 1
A resolvePatternValues() 0 17 2
A resolvePatternValue() 0 20 4
A preparePattern() 0 4 1
A getMatches() 0 4 1
A prepareOkFilename() 0 4 1
A isEqualFilename() 0 4 1
A stripSuffix() 0 4 1
A getOkFilenames() 0 25 4
A reset() 0 4 1
C shouldBeHandled() 0 59 11
B cleanUpOkFile() 0 57 8

How to fix   Complexity   

Complex Class

Complex classes like SimpleFileResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SimpleFileResolver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\FileResolver\BunchFileResolver
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects\FileResolver;
22
23
use TechDivision\Import\Utils\BunchKeys;
24
use TechDivision\Import\Exceptions\LineNotFoundException;
25
use TechDivision\Import\Exceptions\MissingOkFileException;
26
27
/**
28
 * Plugin that processes the subjects.
29
 *
30
 * @author    Tim Wagner <[email protected]>
31
 * @copyright 2016 TechDivision GmbH <[email protected]>
32
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
33
 * @link      https://github.com/techdivision/import
34
 * @link      http://www.techdivision.com
35
 */
36
class SimpleFileResolver extends AbstractFileResolver
37
{
38
39
    /**
40
     * The regular expression used to load the files with.
41
     *
42
     * @var string
43
     */
44
    private $regex = '/^.*\/%s\\.%s$/';
45
46
    /**
47
     * The matches for the last processed CSV filename.
48
     *
49
     * @var array
50
     */
51
    private $matches = array();
52
53
    /**
54
     * Returns the regular expression used to load the files with.
55
     *
56
     * @return string The regular expression
57
     */
58 3
    protected function getRegex()
59
    {
60 3
        return $this->regex;
61
    }
62
63
    /**
64
     * Returns the number of matches found.
65
     *
66
     * @return integer The number of matches
67
     */
68 3
    protected function countMatches()
69
    {
70 3
        return sizeof($this->matches);
71
    }
72
73
    /**
74
     * Adds the passed match to the array with the matches.
75
     *
76
     * @param string $name  The name of the match
77
     * @param string $match The match itself
78
     *
79
     * @return void
80
     */
81 3
    protected function addMatch($name, $match)
82
    {
83 3
        $this->matches[strtolower($name)] = $match;
84 3
    }
85
86
    /**
87
     * Returns the match with the passed name.
88
     *
89
     * @param string $name The name of the match to return
90
     *
91
     * @return string|null The match itself
92
     */
93 3
    protected function getMatch($name)
94
    {
95 3
        if (isset($this->matches[$name])) {
96 3
            return $this->matches[$name];
97
        }
98
    }
99
100
    /**
101
     * Returns the elements the filenames consists of, converted to lowercase.
102
     *
103
     * @return array The array with the filename elements
104
     */
105 3
    protected function getPatternKeys()
106
    {
107
108
        // load the pattern keys from the configuration
109 3
        $patternKeys = $this->getPatternElements();
110
111
        // make sure that they are all lowercase
112 3
        array_walk($patternKeys, function (&$value) {
113 3
            $value = strtolower($value);
114 3
        });
115
116
        // return the pattern keys
117 3
        return $patternKeys;
118
    }
119
120
    /**
121
     * Remove's the passed line from the file with the passed name.
122
     *
123
     * @param string $line     The line to be removed
124
     * @param string $filename The name of the file the line has to be removed
125
     *
126
     * @return void
127
     * @throws \Exception Is thrown, if the file doesn't exists, the line is not found or can not be removed
128
     */
129
    protected function removeLineFromFile($line, $filename)
130
    {
131
        $this->getApplication()->removeLineFromFile($line, $filename);
132
    }
133
134
    /**
135
     * Returns the values to create the regex pattern from.
136
     *
137
     * @return array The array with the pattern values
138
     */
139 3
    protected function resolvePatternValues()
140
    {
141
142
        // initialize the array
143 3
        $elements = array();
144
145
        // load the pattern keys
146 3
        $patternKeys = $this->getPatternKeys();
147
148
        // prepare the pattern values
149 3
        foreach ($patternKeys as $element) {
150 3
            $elements[] = sprintf('(?<%s>%s)', $element, $this->resolvePatternValue($element));
151
        }
152
153
        // return the pattern values
154 3
        return $elements;
155
    }
156
157
    /**
158
     * Resolves the pattern value for the given element name.
159
     *
160
     * @param string $element The element name to resolve the pattern value for
161
     *
162
     * @return string|null The resolved pattern value
163
     */
164 3
    protected function resolvePatternValue($element)
165
    {
166
167
        // query whether or not matches has been found OR the counter element has been passed
168 3
        if ($this->countMatches() === 0 || BunchKeys::COUNTER === $element) {
169
            // prepare the method name for the callback to load the pattern value with
170 3
            $methodName = sprintf('get%s', ucfirst($element));
171
172
            // load the pattern value
173 3
            if (in_array($methodName, get_class_methods($this->getFileResolverConfiguration()))) {
174 3
                return call_user_func(array($this->getFileResolverConfiguration(), $methodName));
175
            }
176
177
            // stop processing
178
            return;
179
        }
180
181
        // try to load the pattern value from the matches
182 3
        return $this->getMatch($element);
183
    }
184
185
    /**
186
     * Prepares and returns the pattern for the regex to load the files from the
187
     * source directory for the passed subject.
188
     *
189
     * @return string The prepared regex pattern
190
     */
191 3
    protected function preparePattern()
192
    {
193 3
        return sprintf($this->getRegex(), implode($this->getElementSeparator(), $this->resolvePatternValues()), $this->getSuffix());
194
    }
195
196
    /**
197
     * Prepares and returns an OK filename from the passed parts.
198
     *
199
     * @param array $parts The parts to concatenate the OK filename from
200
     *
201
     * @return string The OK filename
202
     */
203 1
    protected function prepareOkFilename(array $parts)
204
    {
205 1
        return sprintf('%s/%s.%s', $this->getSourceDir(), implode($this->getElementSeparator(), $parts), $this->getOkFileSuffix());
206
    }
207
208
    /**
209
     * Query whether or not the basename, without suffix, of the passed filenames are equal.
210
     *
211
     * @param string $filename1 The first filename to compare
212
     * @param string $filename2 The second filename to compare
213
     *
214
     * @return boolean TRUE if the passed files basename are equal, else FALSE
215
     */
216 1
    protected function isEqualFilename($filename1, $filename2)
217
    {
218 1
        return $this->stripSuffix($filename1, $this->getSuffix()) === $this->stripSuffix($filename2, $this->getOkFileSuffix());
219
    }
220
221
    /**
222
     * Strips the passed suffix, including the (.), from the filename and returns it.
223
     *
224
     * @param string $filename The filename to return the suffix from
225
     * @param string $suffix   The suffix to return
226
     *
227
     * @return string The filname without the suffix
228
     */
229 1
    protected function stripSuffix($filename, $suffix)
230
    {
231 1
        return basename($filename, sprintf('.%s', $suffix));
232
    }
233
234
    /**
235
     * Return's an array with the names of the expected OK files for the actual subject.
236
     *
237
     * @return array The array with the expected OK filenames
238
     */
239 1
    protected function getOkFilenames()
240
    {
241
242
        // initialize the array for the available okFilenames
243 1
        $okFilenames = array();
244
245
        // prepare the OK filenames based on the found CSV file information
246 1
        for ($i = 1; $i <= sizeof($patternKeys = $this->getPatternKeys()); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
247
            // intialize the array for the parts of the names (prefix, filename + counter)
248 1
            $parts = array();
249
            // load the parts from the matches
250 1
            for ($z = 0; $z < $i; $z++) {
251
                // append the part
252 1
                $parts[] = $this->getMatch($patternKeys[$z]);
253
            }
254
255
            // query whether or not, the OK file exists, if yes append it
256 1
            if (file_exists($okFilename = $this->prepareOkFilename($parts))) {
257 1
                $okFilenames[] = $okFilename;
258
            }
259
        }
260
261
        // prepare and return the pattern for the OK file
262 1
        return $okFilenames;
263
    }
264
265
    /**
266
     * Resets the file resolver to parse another source directory for new files.
267
     *
268
     * @return void
269
     */
270 1
    public function reset()
271
    {
272 1
        $this->matches = array();
273 1
    }
274
275
    /**
276
     * Returns the matches.
277
     *
278
     * @return array The array with the matches
279
     */
280
    public function getMatches()
281
    {
282
        return array_merge(array(BunchKeys::FILENAME => date('Ymd-His'), BunchKeys::COUNTER => 1), $this->matches);
283
    }
284
285
    /**
286
     * Queries whether or not, the passed filename should be handled by the subject.
287
     *
288
     * @param string $filename The filename to query for
289
     *
290
     * @return boolean TRUE if the file should be handled, else FALSE
291
     */
292 3
    public function shouldBeHandled($filename)
293
    {
294
295
        // initialize the array with the matches
296 3
        $matches = array();
297
298
        // update the matches, if the pattern matches
299 3
        if ($result = preg_match($this->preparePattern(), $filename, $matches)) {
300 3
            foreach ($matches as $name => $match) {
301 3
                $this->addMatch($name, $match);
302
            }
303
        }
304
305
        // initialize the flag: we assume that the file is in an OK file
306 3
        $inOkFile = true;
307
308
        // query whether or not the subject requests an OK file
309 3
        if ($this->getSubjectConfiguration()->isOkFileNeeded()) {
310
            // try to load the expected OK filenames
311 1
            if (sizeof($okFilenames = $this->getOkFilenames()) === 0) {
312
                // stop processing, because the needed OK file is NOT available
313
                return false;
314
            }
315
316
            // reset the flag: assumption from initialization is invalid now
317 1
            $inOkFile = false;
318
319
            // iterate over the found OK filenames (should usually be only one, but could be more)
320 1
            foreach ($okFilenames as $okFilename) {
321
                // if the OK filename matches the CSV filename AND the OK file is empty
322 1
                if ($this->isEqualFilename($filename, $okFilename) && filesize($okFilename) === 0) {
323
                    $inOkFile = true;
324
                    break;
325
                }
326
327
                // load the OK file content
328 1
                $okFileLines = file($okFilename);
329
330
                // remove line breaks
331 1
                array_walk($okFileLines, function (&$line) {
332 1
                    $line = trim($line, PHP_EOL);
333 1
                });
334
335
                // query whether or not the OK file contains the filename
336 1
                if (in_array(basename($filename), $okFileLines)) {
337 1
                    $inOkFile = true;
338 1
                    break;
339
                }
340
            }
341
342
            // reset the matches because we've a new bunch
343 1
            if ($inOkFile === false) {
344 1
                $this->reset();
345
            }
346
        }
347
348
        // stop processing, because the filename doesn't match the subjects pattern
349 3
        return ((boolean) $result && $inOkFile);
350
    }
351
352
    /**
353
     * Query whether or not, the passed CSV filename is in the OK file. If the filename was found,
354
     * the OK file will be cleaned-up.
355
     *
356
     * @param string $filename The filename to be cleaned-up
357
     *
358
     * @return void
359
     * @throws \Exception Is thrown, if the passed filename is NOT in the OK file or the OK can not be cleaned-up
360
     */
361
    public function cleanUpOkFile($filename)
362
    {
363
364
        // query whether or not the subject needs an OK file, if yes remove the filename from the file
365
        if ($this->getSubjectConfiguration()->isOkFileNeeded() === false) {
366
            return;
367
        }
368
369
        try {
370
            // try to load the expected OK filenames
371
            if (sizeof($okFilenames = $this->getOkFilenames()) === 0) {
372
                throw new MissingOkFileException(sprintf('Can\'t find a OK filename for file %s', $filename));
373
            }
374
375
            // iterate over the found OK filenames (should usually be only one, but could be more)
376
            foreach ($okFilenames as $okFilename) {
377
                // if the OK filename matches the CSV filename AND the OK file is empty
378
                if ($this->isEqualFilename($filename, $okFilename) && filesize($okFilename) === 0) {
379
                    unlink($okFilename);
380
                    return;
381
                }
382
383
                // else, remove the CSV filename from the OK file
384
                $this->removeLineFromFile(basename($filename), $fh = fopen($okFilename, 'r+'));
0 ignored issues
show
Documentation introduced by
$fh = fopen($okFilename, 'r+') is of type resource, but the function expects a string.

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...
385
                fclose($fh);
386
387
                // if the OK file is empty, delete the file
388
                if (filesize($okFilename) === 0) {
389
                    unlink($okFilename);
390
                }
391
392
                // return immediately
393
                return;
394
            }
395
396
            // throw an exception if either no OK file has been found,
397
            // or the CSV file is not in one of the OK files
398
            throw new \Exception(
399
                sprintf(
400
                    'Can\'t found filename %s in one of the expected OK files: %s',
401
                    $filename,
402
                    implode(', ', $okFilenames)
403
                )
404
            );
405
        } catch (LineNotFoundException $lne) {
406
            // wrap and re-throw the exception
407
            throw new \Exception(
408
                sprintf(
409
                    'Can\'t remove filename %s from OK file: %s',
410
                    $filename,
411
                    $okFilename
412
                ),
413
                null,
414
                $lne
415
            );
416
        }
417
    }
418
}
419