Completed
Pull Request — master (#59)
by Tim
07:47
created

SubjectPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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