Completed
Pull Request — master (#58)
by Tim
02:46
created

SubjectPlugin::isPartOfBunch()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 19
cts 19
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 21
nc 4
nop 2
crap 3
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\Exceptions\LineNotFoundException;
26
use TechDivision\Import\Exceptions\MissingOkFileException;
27
use TechDivision\Import\Subjects\ExportableSubjectInterface;
28
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
29
use TechDivision\Import\Utils\Generators\CoreConfigDataUidGenerator;
30
31
/**
32
 * Plugin that processes the subjects.
33
 *
34
 * @author    Tim Wagner <[email protected]>
35
 * @copyright 2016 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/techdivision/import
38
 * @link      http://www.techdivision.com
39
 */
40
class SubjectPlugin extends AbstractPlugin
41
{
42
43
    /**
44
     * The matches for the last processed CSV filename.
45
     *
46
     * @var array
47
     */
48
    protected $matches = array();
49
50
    /**
51
     * The number of imported bunches.
52
     *
53
     * @var integer
54
     */
55
    protected $bunches = 0;
56
57
    /**
58
     * Process the plugin functionality.
59
     *
60
     * @return void
61
     * @throws \Exception Is thrown, if the plugin can not be processed
62
     */
63
    public function process()
64
    {
65
        try {
66
            // immediately add the PID to lock this import process
67
            $this->lock();
68
69
            // load the plugin's subjects
70
            $subjects = $this->getPluginConfiguration()->getSubjects();
71
72
            // initialize the array for the status
73
            $status = array();
74
75
            // initialize the status information for the subjects
76
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
77
            foreach ($subjects as $subject) {
78
                $status[$subject->getPrefix()] = array();
79
            }
80
81
            // and update it in the registry
82
            $this->getRegistryProcessor()->mergeAttributesRecursive($this->getSerial(), $status);
83
84
            // process all the subjects found in the system configuration
85
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
86
            foreach ($subjects as $subject) {
87
                $this->processSubject($subject);
88
            }
89
90
            // update the number of imported bunches
91
            $this->getRegistryProcessor()->mergeAttributesRecursive(
92
                $this->getSerial(),
93
                array(RegistryKeys::BUNCHES => $this->bunches)
94
            );
95
96
            // stop the application if we don't process ANY bunch
97
            if ($this->bunches === 0) {
98
                $this->getApplication()->stop(
99
                    sprintf(
100
                        'Operation %s has been stopped by %s, because no import files has been found in directory %s',
101
                        $this->getConfiguration()->getOperationName(),
102
                        get_class($this),
103
                        $this->getConfiguration()->getSourceDir()
104
                    )
105
                );
106
            }
107
108
            // finally, if a PID has been set (because CSV files has been found),
109
            // remove it from the PID file to unlock the importer
110
            $this->unlock();
111
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
112
        } catch (\Exception $e) {
113
            // finally, if a PID has been set (because CSV files has been found),
114
            // remove it from the PID file to unlock the importer
115
            $this->unlock();
116
117
            // re-throw the exception
118
            throw $e;
119
        }
120
    }
121
122
    /**
123
     * Process the subject with the passed name/identifier.
124
     *
125
     * We create a new, fresh and separate subject for EVERY file here, because this would be
126
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
127
     * environment.
128
     *
129
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject The subject configuration
130
     *
131
     * @return void
132
     * @throws \Exception Is thrown, if the subject can't be processed
133
     */
134
    protected function processSubject(SubjectConfigurationInterface $subject)
135
    {
136
137
        // clear the filecache
138
        clearstatcache();
139
140
        // load the actual status
141
        $status = $this->getRegistryProcessor()->getAttribute($serial = $this->getSerial());
142
143
        // query whether or not the configured source directory is available
144 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...
145
            throw new \Exception(sprintf('Source directory %s for subject %s is not available!', $sourceDir, $subject->getClassName()));
146
        }
147
148
        // initialize the array with the CSV files found in the source directory
149
        $files = glob(sprintf('%s/*.csv', $sourceDir));
150
151
        // sorting the files for the apropriate order
152
        usort($files, function ($a, $b) {
153
            return strcmp($a, $b);
154
        });
155
156
        // log a debug message
157
        $this->getSystemLogger()->debug(sprintf('Now checking directory %s for files to be imported', $sourceDir));
158
159
        // initialize the bunch number
160
        $bunches = 0;
161
162
        // iterate through all CSV files and process the subjects
163
        foreach ($files as $pathname) {
164
            // query whether or not that the file is part of the actual bunch
165
            if ($this->isPartOfBunch($subject->getPrefix(), $pathname)) {
166
                // initialize the subject and import the bunch
167
                $subjectInstance = $this->subjectFactory($subject);
168
169
                // query whether or not the subject needs an OK file,
170
                // if yes remove the filename from the file
171
                if ($subjectInstance->isOkFileNeeded()) {
172
                    $this->removeFromOkFile($pathname);
173
                }
174
175
                // finally import the CSV file
176
                $subjectInstance->import($serial, $pathname);
177
178
                // query whether or not, we've to export artefacts
179
                if ($subjectInstance instanceof ExportableSubjectInterface) {
180
                    $subjectInstance->export(
181
                        $this->matches[BunchKeys::FILENAME],
182
                        $this->matches[BunchKeys::COUNTER]
183
                    );
184
                }
185
186
                // raise the number of the imported bunches
187
                $bunches++;
188
            }
189
        }
190
191
        // raise the bunch number by the imported bunches
192
        $this->bunches = $this->bunches + $bunches;
193
194
        // reset the matches, because the exported artefacts
195
        $this->matches = array();
196
197
        // and and log a message that the subject has been processed
198
        $this->getSystemLogger()->debug(
199
            sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getClassName(), $bunches)
200
        );
201
    }
202
203
    /**
204
     * Factory method to create new handler instances.
205
     *
206
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subjectConfiguration The subject configuration
207
     *
208
     * @return object The handler instance
209
     */
210
    protected function subjectFactory(SubjectConfigurationInterface $subjectConfiguration)
211
    {
212
213
        // load the subject class name
214
        $className = $subjectConfiguration->getClassName();
215
216
        // initialize the instances
217
        $processor = null;
218
        $systemLoggers = $this->getSystemLoggers();
219
        $registryProcessor = $this->getRegistryProcessor();
220
        $coreConfigDataUidGenerator = new CoreConfigDataUidGenerator();
221
222
        // instanciate and set the product processor, if specified
223
        if ($processorFactory = $subjectConfiguration->getProcessorFactory()) {
224
            $processor = $processorFactory::factory(
225
                $this->getImportProcessor()->getConnection(),
226
                $subjectConfiguration
227
            );
228
        }
229
230
        // initialize a new handler with the passed class name
231
        return new $className(
232
            $subjectConfiguration,
233
            $registryProcessor,
234
            $coreConfigDataUidGenerator,
235
            $systemLoggers,
236
            $processor
237
        );
238
    }
239
240
    /**
241
     * Queries whether or not, the passed filename is part of a bunch or not.
242
     *
243
     * @param string $prefix   The prefix to query for
244
     * @param string $filename The filename to query for
245
     *
246
     * @return boolean TRUE if the filename is part, else FALSE
247
     */
248 2
    protected function isPartOfBunch($prefix, $filename)
249
    {
250
251
        // initialize the pattern
252 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...
253
254
        // query whether or not, this is the first file to be processed
255 2
        if (sizeof($this->matches) === 0) {
256
            // initialize the pattern to query whether the FIRST file has to be processed or not
257 2
            $pattern = sprintf(
258 2
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.csv$/',
259 2
                BunchKeys::PREFIX,
260
                $prefix,
261 2
                BunchKeys::FILENAME,
262 2
                BunchKeys::COUNTER
263
            );
264
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
265
        } else {
266
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
267 2
            $pattern = sprintf(
268 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.csv$/',
269 2
                BunchKeys::PREFIX,
270 2
                $this->matches[BunchKeys::PREFIX],
271 2
                BunchKeys::FILENAME,
272 2
                $this->matches[BunchKeys::FILENAME],
273 2
                BunchKeys::COUNTER
274
            );
275
        }
276
277
        // initialize the array for the matches
278 2
        $matches = array();
279
280
        // update the matches, if the pattern matches
281 2
        if ($result = preg_match($pattern, $filename, $matches)) {
282 2
            $this->matches = $matches;
283
        }
284
285
        // stop processing, if the filename doesn't match
286 2
        return (boolean) $result;
287
    }
288
289
    /**
290
     * Return's an array with the names of the expected OK files for the actual subject.
291
     *
292
     * @return array The array with the expected OK filenames
293
     */
294
    protected function getOkFilenames()
295
    {
296
297
        // load the array with the available bunch keys
298
        $bunchKeys = BunchKeys::getAllKeys();
299
300
        // initialize the array for the available okFilenames
301
        $okFilenames = array();
302
303
        // prepare the OK filenames based on the found CSV file information
304
        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...
305
            // intialize the array for the parts of the names (prefix, filename + counter)
306
            $parts = array();
307
            // load the parts from the matches
308
            for ($z = 0; $z < $i; $z++) {
309
                $parts[] = $this->matches[$bunchKeys[$z]];
310
            }
311
312
            // query whether or not, the OK file exists, if yes append it
313
            if (file_exists($okFilename = sprintf('%s/%s.ok', $this->getSourceDir(), implode('_', $parts)))) {
314
                $okFilenames[] = $okFilename;
315
            }
316
        }
317
318
        // prepare and return the pattern for the OK file
319
        return $okFilenames;
320
    }
321
322
    /**
323
     * Query whether or not, the passed CSV filename is in the OK file. If the filename was found,
324
     * it'll be returned and the method return TRUE.
325
     *
326
     * If the filename is NOT in the OK file, the method return's FALSE and the CSV should NOT be
327
     * imported/moved.
328
     *
329
     * @param string $filename The CSV filename to query for
330
     *
331
     * @return void
332
     * @throws \Exception Is thrown, if the passed filename is NOT in the OK file or it can NOT be removed from it
333
     */
334
    protected function removeFromOkFile($filename)
335
    {
336
337
        try {
338
            // try to load the expected OK filenames
339
            if (sizeof($okFilenames = $this->getOkFilenames()) === 0) {
340
                throw new MissingOkFileException(sprintf('Can\'t find a OK filename for file %s', $filename));
341
            }
342
343
            // iterate over the found OK filenames (should usually be only one, but could be more)
344
            foreach ($okFilenames as $okFilename) {
345
                // if the OK filename matches the CSV filename AND the OK file is empty
346
                if (basename($filename, '.csv') === basename($okFilename, '.ok') && filesize($okFilename) === 0) {
347
                    unlink($okFilename);
348
                    return;
349
                }
350
351
                // else, remove the CSV filename from the OK file
352
                $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...
353
                fclose($fh);
354
355
                // if the OK file is empty, delete the file
356
                if (filesize($okFilename) === 0) {
357
                    unlink($okFilename);
358
                }
359
360
                // return immediately
361
                return;
362
            }
363
364
            // throw an exception if either no OK file has been found,
365
            // or the CSV file is not in one of the OK files
366
            throw new \Exception(
367
                sprintf(
368
                    'Can\'t found filename %s in one of the expected OK files: %s',
369
                    $filename,
370
                    implode(', ', $okFilenames)
371
                )
372
            );
373
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
374
        } catch (LineNotFoundException $lne) {
375
            // wrap and re-throw the exception
376
            throw new \Exception(
377
                sprintf(
378
                    'Can\'t remove filename %s from OK file: %s',
379
                    $filename,
380
                    $okFilename
381
                ),
382
                null,
383
                $lne
384
            );
385
        }
386
    }
387
}
388