Completed
Pull Request — master (#105)
by Tim
11:17
created

SubjectPlugin::processSubject()   C

Complexity

Conditions 7
Paths 34

Size

Total Lines 72
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 72
ccs 35
cts 35
cp 1
rs 6.7427
c 0
b 0
f 0
cc 7
eloc 28
nc 34
nop 1
crap 7

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
/**
4
 * TechDivision\Import\Plugins\SubjectPlugin
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\Plugins;
22
23
use TechDivision\Import\Utils\BunchKeys;
24
use TechDivision\Import\Utils\RegistryKeys;
25
use TechDivision\Import\ApplicationInterface;
26
use TechDivision\Import\Callbacks\CallbackVisitor;
27
use TechDivision\Import\Observers\ObserverVisitor;
28
use TechDivision\Import\Subjects\SubjectFactoryInterface;
29
use TechDivision\Import\Exceptions\LineNotFoundException;
30
use TechDivision\Import\Exceptions\MissingOkFileException;
31
use TechDivision\Import\Subjects\ExportableSubjectInterface;
32
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
33
34
/**
35
 * Plugin that processes the subjects.
36
 *
37
 * @author    Tim Wagner <[email protected]>
38
 * @copyright 2016 TechDivision GmbH <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/techdivision/import
41
 * @link      http://www.techdivision.com
42
 */
43
class SubjectPlugin extends AbstractPlugin
44
{
45
46
    /**
47
     * The matches for the last processed CSV filename.
48
     *
49
     * @var array
50
     */
51
    protected $matches = array();
52
53
    /**
54
     * The number of imported bunches.
55
     *
56
     * @var integer
57
     */
58
    protected $bunches = 0;
59
60
    /**
61
     * The callback visitor instance.
62
     *
63
     * @var \TechDivision\Import\Callbacks\CallbackVisitor
64
     */
65
    protected $callbackVisitor;
66
67
    /**
68
     * The observer visitor instance.
69
     *
70
     * @var \TechDivision\Import\Observers\ObserverVisitor
71
     */
72
    protected $observerVisitor;
73
74
    /**
75
     * The subject factory instance.
76
     *
77
     * @var \TechDivision\Import\Subjects\SubjectFactoryInterface
78
     */
79
    protected $subjectFactory;
80
81
    /**
82
     * Initializes the plugin with the application instance.
83
     *
84
     * @param \TechDivision\Import\ApplicationInterface             $application     The application instance
85
     * @param \TechDivision\Import\Callbacks\CallbackVisitor        $callbackVisitor The callback visitor instance
86
     * @param \TechDivision\Import\Observers\ObserverVisitor        $observerVisitor The observer visitor instance
87
     * @param \TechDivision\Import\Subjects\SubjectFactoryInterface $subjectFactory  The subject factory instance
88
     */
89 6
    public function __construct(
90
        ApplicationInterface $application,
91
        CallbackVisitor $callbackVisitor,
92
        ObserverVisitor $observerVisitor,
93
        SubjectFactoryInterface $subjectFactory
94
    ) {
95
96
        // call the parent constructor
97 6
        parent::__construct($application);
98
99
        // initialize the callback/observer visitors
100 6
        $this->callbackVisitor = $callbackVisitor;
101 6
        $this->observerVisitor = $observerVisitor;
102 6
        $this->subjectFactory = $subjectFactory;
103 6
    }
104
105
106
    /**
107
     * Process the plugin functionality.
108
     *
109
     * @return void
110
     * @throws \Exception Is thrown, if the plugin can not be processed
111
     */
112 4
    public function process()
113
    {
114
        try {
115
            // immediately add the PID to lock this import process
116 4
            $this->lock();
117
118
            // load the plugin's subjects
119 4
            $subjects = $this->getPluginConfiguration()->getSubjects();
120
121
            // initialize the array for the status
122 4
            $status = array();
123
124
            // initialize the status information for the subjects
125
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
126 4
            foreach ($subjects as $subject) {
127 3
                $status[$subject->getPrefix()] = array();
128 4
            }
129
130
            // and update it in the registry
131 4
            $this->getRegistryProcessor()->mergeAttributesRecursive($this->getSerial(), $status);
132
133
            // process all the subjects found in the system configuration
134
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
135 4
            foreach ($subjects as $subject) {
136 3
                $this->processSubject($subject);
137 2
            }
138
139
            // update the number of imported bunches
140 2
            $this->getRegistryProcessor()->mergeAttributesRecursive(
141 2
                $this->getSerial(),
142 2
                array(RegistryKeys::BUNCHES => $this->bunches)
143 2
            );
144
145
            // stop the application if we don't process ANY bunch
146 2
            if ($this->bunches === 0) {
147 1
                $this->getApplication()->stop(
148 1
                    sprintf(
149 1
                        'Operation %s has been stopped by %s, because no import files can be found in directory %s',
150 1
                        $this->getConfiguration()->getOperationName(),
151 1
                        get_class($this),
152 1
                        $this->getConfiguration()->getSourceDir()
153 1
                    )
154 1
                );
155 1
            }
156
157
            // finally, if a PID has been set (because CSV files has been found),
158
            // remove it from the PID file to unlock the importer
159 2
            $this->unlock();
160
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
161 4
        } catch (\Exception $e) {
162
            // finally, if a PID has been set (because CSV files has been found),
163
            // remove it from the PID file to unlock the importer
164 2
            $this->unlock();
165
166
            // re-throw the exception
167 2
            throw $e;
168
        }
169 2
    }
170
171
    /**
172
     * Loads the files from the source directory and return's them sorted.
173
     *
174
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject The source directory to parse for files
175
     *
176
     * @return array The array with the files matching the subjects suffix
177
     * @throws \Exception Is thrown, when the source directory is NOT available
178
     */
179 3
    protected function loadFiles(SubjectConfigurationInterface $subject)
180
    {
181
182
        // clear the filecache
183 3
        clearstatcache();
184
185
        // load the actual status
186 3
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
187
188
        // query whether or not the configured source directory is available
189 3 View Code Duplication
        if (!is_dir($sourceDir = $status[RegistryKeys::SOURCE_DIRECTORY])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
190 1
            throw new \Exception(sprintf('Source directory %s for subject %s is not available!', $sourceDir, $subject->getId()));
191
        }
192
193
        // initialize the array with the files matching the suffix found in the source directory
194 2
        $files = glob(sprintf('%s/*.%s', $sourceDir, $subject->getSuffix()));
195
196
        // sort the files for the apropriate order
197 2
        usort($files, function ($a, $b) {
198
            return strcmp($a, $b);
199 2
        });
200
201
        // return the sorted files
202 2
        return $files;
203
    }
204
205
    /**
206
     * Process the subject with the passed name/identifier.
207
     *
208
     * We create a new, fresh and separate subject for EVERY file here, because this would be
209
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
210
     * environment.
211
     *
212
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject The subject configuration
213
     *
214
     * @return void
215
     * @throws \Exception Is thrown, if the subject can't be processed
216
     */
217 3
    protected function processSubject(SubjectConfigurationInterface $subject)
218
    {
219
220
        // initialize the bunch number and the serial
221 3
        $bunches = 0;
222 3
        $serial = $this->getSerial();
223
224
        // load the files
225 3
        $files = $this->loadFiles($subject);
226
227
        // iterate through all CSV files and process the subjects
228 2
        foreach ($files as $pathname) {
229
            // query whether or not that the file is part of the actual bunch
230 2
            if ($this->isPartOfBunch($subject->getPrefix(), $subject->getSuffix(), $pathname)) {
231
                try {
232
                    // initialize the subject and import the bunch
233 2
                    $subjectInstance = $this->subjectFactory->createSubject($subject);
234
235
                    // setup the subject instance
236 2
                    $subjectInstance->setUp($serial);
237
238
                    // initialize the callbacks/observers
239 2
                    $this->callbackVisitor->visit($subjectInstance);
240 2
                    $this->observerVisitor->visit($subjectInstance);
241
242
                    // query whether or not the subject needs an OK file,
243
                    // if yes remove the filename from the file
244 2
                    if ($subjectInstance->isOkFileNeeded()) {
245 2
                        $this->removeFromOkFile($pathname, $subject->getSuffix());
246 2
                    }
247
248
                    // finally import the CSV file
249 2
                    $subjectInstance->import($serial, $pathname);
250
251
                    // query whether or not, we've to export artefacts
252 2
                    if ($subjectInstance instanceof ExportableSubjectInterface) {
253 2
                        $subjectInstance->export(
254 2
                            $this->matches[BunchKeys::FILENAME],
255 2
                            $this->matches[BunchKeys::COUNTER]
256 2
                        );
257 1
                    }
258
259
                    // raise the number of the imported bunches
260 1
                    $bunches++;
261
262
                    // tear down the subject instance
263 1
                    $subjectInstance->tearDown($serial);
264
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
265 2
                } catch (\Exception $e) {
266
                    // query whether or not, we've to export artefacts
267 1
                    if ($subjectInstance instanceof ExportableSubjectInterface) {
268
                        // tear down the subject instance
269 1
                        $subjectInstance->tearDown($serial);
270 1
                    }
271
272
                    // re-throw the exception
273 1
                    throw $e;
274
                }
275 1
            }
276 1
        }
277
278
        // raise the bunch number by the imported bunches
279 1
        $this->bunches = $this->bunches + $bunches;
280
281
        // reset the matches, because the exported artefacts
282 1
        $this->matches = array();
283
284
        // and and log a message that the subject has been processed
285 1
        $this->getSystemLogger()->debug(
286 1
            sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getId(), $bunches)
287 1
        );
288 1
    }
289
290
    /**
291
     * Queries whether or not, the passed filename is part of a bunch or not.
292
     *
293
     * @param string $prefix   The prefix to query for
294
     * @param string $suffix   The suffix to query for
295
     * @param string $filename The filename to query for
296
     *
297
     * @return boolean TRUE if the filename is part, else FALSE
298
     */
299 4
    protected function isPartOfBunch($prefix, $suffix, $filename)
300
    {
301
302
        // initialize the pattern
303 4
        $pattern = '';
0 ignored issues
show
Unused Code introduced by
$pattern is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
304
305
        // query whether or not, this is the first file to be processed
306 4
        if (sizeof($this->matches) === 0) {
307
            // initialize the pattern to query whether the FIRST file has to be processed or not
308 4
            $pattern = sprintf(
309 4
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.%s$/',
310 4
                BunchKeys::PREFIX,
311 4
                $prefix,
312 4
                BunchKeys::FILENAME,
313 4
                BunchKeys::COUNTER,
314
                $suffix
315 4
            );
316
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
317 4
        } else {
318
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
319 2
            $pattern = sprintf(
320 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.%s$/',
321 2
                BunchKeys::PREFIX,
322 2
                $this->matches[BunchKeys::PREFIX],
323 2
                BunchKeys::FILENAME,
324 2
                $this->matches[BunchKeys::FILENAME],
325 2
                BunchKeys::COUNTER,
326
                $suffix
327 2
            );
328
        }
329
330
        // initialize the array for the matches
331 4
        $matches = array();
332
333
        // update the matches, if the pattern matches
334 4
        if ($result = preg_match($pattern, $filename, $matches)) {
335 4
            $this->matches = $matches;
336 4
        }
337
338
        // stop processing, if the filename doesn't match
339 4
        return (boolean) $result;
340
    }
341
342
    /**
343
     * Return's an array with the names of the expected OK files for the actual subject.
344
     *
345
     * @return array The array with the expected OK filenames
346
     */
347 2
    protected function getOkFilenames()
348
    {
349
350
        // load the array with the available bunch keys
351 2
        $bunchKeys = BunchKeys::getAllKeys();
352
353
        // initialize the array for the available okFilenames
354 2
        $okFilenames = array();
355
356
        // prepare the OK filenames based on the found CSV file information
357 2
        for ($i = 1; $i <= sizeof($bunchKeys); $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...
358
            // intialize the array for the parts of the names (prefix, filename + counter)
359 2
            $parts = array();
360
            // load the parts from the matches
361 2
            for ($z = 0; $z < $i; $z++) {
362 2
                $parts[] = $this->matches[$bunchKeys[$z]];
363 2
            }
364
365
            // query whether or not, the OK file exists, if yes append it
366 2
            if (file_exists($okFilename = sprintf('%s/%s.ok', $this->getSourceDir(), implode('_', $parts)))) {
367 2
                $okFilenames[] = $okFilename;
368 2
            }
369 2
        }
370
371
        // prepare and return the pattern for the OK file
372 2
        return $okFilenames;
373
    }
374
375
    /**
376
     * Query whether or not, the passed CSV filename is in the OK file. If the filename was found,
377
     * it'll be returned and the method return TRUE.
378
     *
379
     * If the filename is NOT in the OK file, the method return's FALSE and the CSV should NOT be
380
     * imported/moved.
381
     *
382
     * @param string $filename The CSV filename to query for
383
     * @param string $suffix   The CSF filename suffix, csv by default
384
     *
385
     * @return void
386
     * @throws \Exception Is thrown, if the passed filename is NOT in the OK file or it can NOT be removed from it
387
     */
388 2
    protected function removeFromOkFile($filename, $suffix)
389
    {
390
391
        try {
392
            // try to load the expected OK filenames
393 2
            if (sizeof($okFilenames = $this->getOkFilenames()) === 0) {
394
                throw new MissingOkFileException(sprintf('Can\'t find a OK filename for file %s', $filename));
395
            }
396
397
            // iterate over the found OK filenames (should usually be only one, but could be more)
398 2
            foreach ($okFilenames as $okFilename) {
399
                // if the OK filename matches the CSV filename AND the OK file is empty
400 2
                if (basename($filename, sprintf('.%s', $suffix)) === basename($okFilename, '.ok') && filesize($okFilename) === 0) {
401
                    unlink($okFilename);
402
                    return;
403
                }
404
405
                // else, remove the CSV filename from the OK file
406 2
                $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...
407 2
                fclose($fh);
408
409
                // if the OK file is empty, delete the file
410 2
                if (filesize($okFilename) === 0) {
411
                    unlink($okFilename);
412
                }
413
414
                // return immediately
415 2
                return;
416
            }
417
418
            // throw an exception if either no OK file has been found,
419
            // or the CSV file is not in one of the OK files
420
            throw new \Exception(
421
                sprintf(
422
                    'Can\'t found filename %s in one of the expected OK files: %s',
423
                    $filename,
424
                    implode(', ', $okFilenames)
425
                )
426
            );
427
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
428
        } catch (LineNotFoundException $lne) {
429
            // wrap and re-throw the exception
430
            throw new \Exception(
431
                sprintf(
432
                    'Can\'t remove filename %s from OK file: %s',
433
                    $filename,
434
                    $okFilename
435
                ),
436
                null,
437
                $lne
438
            );
439
        }
440
    }
441
}
442