Completed
Pull Request — master (#76)
by Tim
02:59
created

SubjectPlugin::process()   B

Complexity

Conditions 5
Paths 35

Size

Total Lines 58
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 58
ccs 0
cts 24
cp 0
rs 8.7274
cc 5
eloc 24
nc 35
nop 0
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
/**
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\App\Utils\SynteticServiceKeys;
27
use TechDivision\Import\Callbacks\CallbackVisitor;
28
use TechDivision\Import\Observers\ObserverVisitor;
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
use TechDivision\Import\Configuration\PluginConfigurationInterface;
34
use TechDivision\Import\Subjects\SubjectInterface;
35
36
/**
37
 * Plugin that processes the subjects.
38
 *
39
 * @author    Tim Wagner <[email protected]>
40
 * @copyright 2016 TechDivision GmbH <[email protected]>
41
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
42
 * @link      https://github.com/techdivision/import
43
 * @link      http://www.techdivision.com
44
 */
45
class SubjectPlugin extends AbstractPlugin
46
{
47
48
    /**
49
     * The matches for the last processed CSV filename.
50
     *
51
     * @var array
52
     */
53
    protected $matches = array();
54
55
    /**
56
     * The number of imported bunches.
57
     *
58
     * @var integer
59
     */
60
    protected $bunches = 0;
61
62
    /**
63
     * The callback visitor instance.
64
     *
65
     * @var \TechDivision\Import\Callbacks\CallbackVisitor
66
     */
67
    protected $callbackVisitor;
68
69
    /**
70
     * The observer visitor instance.
71
     *
72
     * @var \TechDivision\Import\Observers\ObserverVisitor
73
     */
74
    protected $observerVisitor;
75
76
    /**
77
     * Initializes the plugin with the application instance.
78
     *
79
     * @param \TechDivision\Import\ApplicationInterface                       $application         The application instance
80
     * @param \TechDivision\Import\Configuration\PluginConfigurationInterface $pluginConfiguration The plugin configuration instance
81
     * @param \TechDivision\Import\Callbacks\CallbackVisitor                  $callbackVisitor     The callback visitor instance
82
     * @param \TechDivision\Import\Observers\ObserverVisitor                  $observerVisitor     The observer visitor instance
83
     */
84 2
    public function __construct(
85
        ApplicationInterface $application,
86
        PluginConfigurationInterface $pluginConfiguration,
87
        CallbackVisitor $callbackVisitor,
88
        ObserverVisitor $observerVisitor
89
    ) {
90
91
        // call the parent constructor
92 2
        parent::__construct($application, $pluginConfiguration);
93
94
        // initialize the callback/observer visitors
95 2
        $this->callbackVisitor = $callbackVisitor;
96 2
        $this->observerVisitor = $observerVisitor;
97 2
    }
98
99
100
    /**
101
     * Process the plugin functionality.
102
     *
103
     * @return void
104
     * @throws \Exception Is thrown, if the plugin can not be processed
105
     */
106
    public function process()
107
    {
108
        try {
109
            // immediately add the PID to lock this import process
110
            $this->lock();
111
112
            // load the plugin's subjects
113
            $subjects = $this->getPluginConfiguration()->getSubjects();
114
115
            // initialize the array for the status
116
            $status = array();
117
118
            // initialize the status information for the subjects
119
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
120
            foreach ($subjects as $subject) {
121
                $status[$subject->getPrefix()] = array();
122
            }
123
124
            // and update it in the registry
125
            $this->getRegistryProcessor()->mergeAttributesRecursive($this->getSerial(), $status);
126
127
            // process all the subjects found in the system configuration
128
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
129
            foreach ($subjects as $subject) {
130
                $this->processSubject($subject);
131
            }
132
133
            // update the number of imported bunches
134
            $this->getRegistryProcessor()->mergeAttributesRecursive(
135
                $this->getSerial(),
136
                array(RegistryKeys::BUNCHES => $this->bunches)
137
            );
138
139
            // stop the application if we don't process ANY bunch
140
            if ($this->bunches === 0) {
141
                $this->getApplication()->stop(
142
                    sprintf(
143
                        'Operation %s has been stopped by %s, because no import files has been found in directory %s',
144
                        $this->getConfiguration()->getOperationName(),
145
                        get_class($this),
146
                        $this->getConfiguration()->getSourceDir()
147
                    )
148
                );
149
            }
150
151
            // finally, if a PID has been set (because CSV files has been found),
152
            // remove it from the PID file to unlock the importer
153
            $this->unlock();
154
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
155
        } catch (\Exception $e) {
156
            // finally, if a PID has been set (because CSV files has been found),
157
            // remove it from the PID file to unlock the importer
158
            $this->unlock();
159
160
            // re-throw the exception
161
            throw $e;
162
        }
163
    }
164
165
    /**
166
     * Process the subject with the passed name/identifier.
167
     *
168
     * We create a new, fresh and separate subject for EVERY file here, because this would be
169
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
170
     * environment.
171
     *
172
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject The subject configuration
173
     *
174
     * @return void
175
     * @throws \Exception Is thrown, if the subject can't be processed
176
     */
177
    protected function processSubject(SubjectConfigurationInterface $subject)
178
    {
179
180
        // clear the filecache
181
        clearstatcache();
182
183
        // load the actual status
184
        $status = $this->getRegistryProcessor()->getAttribute($serial = $this->getSerial());
185
186
        // query whether or not the configured source directory is available
187 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...
188
            throw new \Exception(sprintf('Source directory %s for subject %s is not available!', $sourceDir, $subject->getId()));
189
        }
190
191
        // initialize the array with the CSV files found in the source directory
192
        $files = glob(sprintf('%s/*.csv', $sourceDir));
193
194
        // sorting the files for the apropriate order
195
        usort($files, function ($a, $b) {
196
            return strcmp($a, $b);
197
        });
198
199
        // log a debug message
200
        $this->getSystemLogger()->debug(sprintf('Now checking directory %s for files to be imported', $sourceDir));
201
202
        // initialize the bunch number
203
        $bunches = 0;
204
205
        // load the container instance from the application
206
        $container = $this->getApplication()->getContainer();
0 ignored issues
show
Bug introduced by
The method getContainer() does not seem to exist on object<TechDivision\Import\ApplicationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Unused Code introduced by
$container 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...
207
208
        // iterate through all CSV files and process the subjects
209
        foreach ($files as $pathname) {
210
            // query whether or not that the file is part of the actual bunch
211
            if ($this->isPartOfBunch($subject->getPrefix(), $pathname)) {
212
                try {
213
                    // initialize the subject and import the bunch
214
                    $subjectInstance = $this->subjectFactory($subject);
215
216
                    // setup the subject instance
217
                    $subjectInstance->setUp($serial);
218
219
                    // initialize the callbacks/observers
220
                    $this->callbackVisitor->visit($subjectInstance);
221
                    $this->observerVisitor->visit($subjectInstance);
222
223
                    // query whether or not the subject needs an OK file,
224
                    // if yes remove the filename from the file
225
                    if ($subjectInstance->isOkFileNeeded()) {
226
                        $this->removeFromOkFile($pathname);
227
                    }
228
229
                    // finally import the CSV file
230
                    $subjectInstance->import($serial, $pathname);
231
232
                    // query whether or not, we've to export artefacts
233
                    if ($subjectInstance instanceof ExportableSubjectInterface) {
234
                        $subjectInstance->export(
235
                            $this->matches[BunchKeys::FILENAME],
236
                            $this->matches[BunchKeys::COUNTER]
237
                        );
238
                    }
239
240
                    // raise the number of the imported bunches
241
                    $bunches++;
242
243
                    // tear down the subject instance
244
                    $subjectInstance->tearDown($serial);
245
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
246
                } catch (\Exception $e) {
247
                    // query whether or not, we've to export artefacts
248
                    if ($subjectInstance instanceof ExportableSubjectInterface) {
249
                        // tear down the subject instance
250
                        $subjectInstance->tearDown($serial);
251
                    }
252
253
                    // re-throw the exception
254
                    throw $e;
255
                }
256
            }
257
        }
258
259
        // raise the bunch number by the imported bunches
260
        $this->bunches = $this->bunches + $bunches;
261
262
        // reset the matches, because the exported artefacts
263
        $this->matches = array();
264
265
        // and and log a message that the subject has been processed
266
        $this->getSystemLogger()->debug(
267
            sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getId(), $bunches)
268
        );
269
    }
270
271
    /**
272
     * Factory method to create new subject instance.
273
     *
274
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subjectConfiguration The subject configuration
275
     *
276
     * @return \ TechDivision\Import\Subjects\SubjectInterface The subject instance
277
     */
278
    protected function subjectFactory(SubjectConfigurationInterface $subjectConfiguration)
279
    {
280
281
        // load the DI container instance
282
        $container = $this->getApplication()->getContainer();
0 ignored issues
show
Bug introduced by
The method getContainer() does not seem to exist on object<TechDivision\Import\ApplicationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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