Completed
Push — master ( f2c022...d5f4e5 )
by Tim
11s
created

SubjectPlugin::subjectFactory()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 15
cp 0
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 14
nc 2
nop 1
crap 6
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\Subjects\ExportableSubjectInterface;
26
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
27
28
/**
29
 * Plugin that processes the subjects.
30
 *
31
 * @author    Tim Wagner <[email protected]>
32
 * @copyright 2016 TechDivision GmbH <[email protected]>
33
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
34
 * @link      https://github.com/techdivision/import
35
 * @link      http://www.techdivision.com
36
 */
37
class SubjectPlugin extends AbstractPlugin
38
{
39
40
    /**
41
     * The matches for the last processed CSV filename.
42
     *
43
     * @var array
44
     */
45
    protected $matches = array();
46
47
    /**
48
     * The number of imported bunches.
49
     *
50
     * @var integer
51
     */
52
    protected $bunches = 0;
53
54
    /**
55
     * Process the plugin functionality.
56
     *
57
     * @return void
58
     * @throws \Exception Is thrown, if the plugin can not be processed
59
     */
60
    public function process()
61
    {
62
        try {
63
            // immediately add the PID to lock this import process
64
            $this->lock();
65
66
            // load system logger and registry
67
            $importProcessor = $this->getImportProcessor();
68
69
            // start the transaction
70
            $importProcessor->getConnection()->beginTransaction();
71
72
            // load the plugin's subjects
73
            $subjects = $this->getPluginConfiguration()->getSubjects();
74
75
            // initialize the array for the status
76
            $status = array();
77
78
            // initialize the status information for the subjects
79
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
80
            foreach ($subjects as $subject) {
81
                $status[$subject->getPrefix()] = array();
82
            }
83
84
            // and update it in the registry
85
            $this->getRegistryProcessor()->mergeAttributesRecursive($this->getSerial(), $status);
86
87
            // process all the subjects found in the system configuration
88
            /** @var \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject */
89
            foreach ($subjects as $subject) {
90
                $this->processSubject($subject);
91
            }
92
93
            // update the number of imported bunches
94
            $this->getRegistryProcessor()->mergeAttributesRecursive(
95
                $this->getSerial(),
96
                array(RegistryKeys::BUNCHES => $this->bunches)
97
            );
98
99
            // finally, if a PID has been set (because CSV files has been found),
100
            // remove it from the PID file to unlock the importer
101
            $this->unlock();
102
103
            // commit the transaction
104
            $importProcessor->getConnection()->commit();
105
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
106
        } catch (\Exception $e) {
107
            // finally, if a PID has been set (because CSV files has been found),
108
            // remove it from the PID file to unlock the importer
109
            $this->unlock();
110
111
            // rollback the transaction
112
            $importProcessor->getConnection()->rollBack();
0 ignored issues
show
Bug introduced by
The variable $importProcessor does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
113
114
            // re-throw the exception
115
            throw $e;
116
        }
117
    }
118
119
    /**
120
     * Process the subject with the passed name/identifier.
121
     *
122
     * We create a new, fresh and separate subject for EVERY file here, because this would be
123
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
124
     * environment.
125
     *
126
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subject The subject configuration
127
     *
128
     * @return void
129
     * @throws \Exception Is thrown, if the subject can't be processed
130
     */
131
    protected function processSubject(SubjectConfigurationInterface $subject)
132
    {
133
134
        // clear the filecache
135
        clearstatcache();
136
137
        // load the actual status
138
        $status = $this->getRegistryProcessor()->getAttribute($serial = $this->getSerial());
139
140
        // query whether or not the configured source directory is available
141 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...
142
            throw new \Exception(sprintf('Source directory %s for subject %s is not available!', $sourceDir, $subject->getClassName()));
143
        }
144
145
        // initialize the array with the CSV files found in the source directory
146
        $files = glob(sprintf('%s/*.csv', $sourceDir));
147
148
        // sorting the files for the apropriate order
149
        usort($files, function ($a, $b) {
150
            return strcmp($a, $b);
151
        });
152
153
        // log a debug message
154
        $this->getSystemLogger()->debug(sprintf('Now checking directory %s for files to be imported', $sourceDir));
155
156
        // initialize the bunch number
157
        $bunches = 0;
158
159
        // iterate through all CSV files and process the subjects
160
        foreach ($files as $pathname) {
161
            // query whether or not that the file is part of the actual bunch
162
            if ($this->isPartOfBunch($subject->getPrefix(), $pathname)) {
163
                // initialize the subject and import the bunch
164
                $subjectInstance = $this->subjectFactory($subject);
165
166
                // query whether or not the subject needs an OK file,
167
                // if yes remove the filename from the file
168
                if ($subjectInstance->isOkFileNeeded()) {
169
                    $this->removeFromOkFile($pathname);
170
                }
171
172
                // finally import the CSV file
173
                $subjectInstance->import($serial, $pathname);
174
175
                // query whether or not, we've to export artefacts
176
                if ($subjectInstance instanceof ExportableSubjectInterface) {
177
                    $subjectInstance->export(
178
                        $this->matches[BunchKeys::FILENAME],
179
                        $this->matches[BunchKeys::COUNTER]
180
                    );
181
                }
182
183
                // raise the number of the imported bunches
184
                $bunches++;
185
            }
186
        }
187
188
        // raise the bunch number by the imported bunches
189
        $this->bunches = $this->bunches + $bunches;
190
191
        // reset the matches, because the exported artefacts
192
        $this->matches = array();
193
194
        // and and log a message that the subject has been processed
195
        $this->getSystemLogger()->debug(
196
            sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getClassName(), $bunches)
197
        );
198
    }
199
200
    /**
201
     * Factory method to create new handler instances.
202
     *
203
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $subjectConfiguration The subject configuration
204
     *
205
     * @return object The handler instance
206
     */
207
    protected function subjectFactory(SubjectConfigurationInterface $subjectConfiguration)
208
    {
209
210
        // load the subject class name
211
        $className = $subjectConfiguration->getClassName();
212
213
        // initialize the instances
214
        $processor = null;
215
        $systemLogger = $this->getSystemLogger();
216
        $registryProcessor = $this->getRegistryProcessor();
217
218
        // instanciate and set the product processor, if specified
219
        if ($processorFactory = $subjectConfiguration->getProcessorFactory()) {
220
            $processor = $processorFactory::factory(
221
                $this->getImportProcessor()->getConnection(),
222
                $subjectConfiguration
223
            );
224
        }
225
226
        // initialize a new handler with the passed class name
227
        return new $className(
228
            $systemLogger,
229
            $subjectConfiguration,
230
            $registryProcessor,
231
            $processor
232
        );
233
    }
234
235
    /**
236
     * Queries whether or not, the passed filename is part of a bunch or not.
237
     *
238
     * @param string $prefix   The prefix to query for
239
     * @param string $filename The filename to query for
240
     *
241
     * @return boolean TRUE if the filename is part, else FALSE
242
     */
243 2
    protected function isPartOfBunch($prefix, $filename)
244
    {
245
246
        // initialize the pattern
247 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...
248
249
        // query whether or not, this is the first file to be processed
250 2
        if (sizeof($this->matches) === 0) {
251
            // initialize the pattern to query whether the FIRST file has to be processed or not
252 2
            $pattern = sprintf(
253 2
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.csv$/',
254 2
                BunchKeys::PREFIX,
255 2
                $prefix,
256 2
                BunchKeys::FILENAME,
257
                BunchKeys::COUNTER
258 2
            );
259
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
260 2
        } else {
261
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
262 2
            $pattern = sprintf(
263 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.csv$/',
264 2
                BunchKeys::PREFIX,
265 2
                $this->matches[BunchKeys::PREFIX],
266 2
                BunchKeys::FILENAME,
267 2
                $this->matches[BunchKeys::FILENAME],
268
                BunchKeys::COUNTER
269 2
            );
270
        }
271
272
        // initialize the array for the matches
273 2
        $matches = array();
274
275
        // update the matches, if the pattern matches
276 2
        if ($result = preg_match($pattern, $filename, $matches)) {
277 2
            $this->matches = $matches;
278 2
        }
279
280
        // stop processing, if the filename doesn't match
281 2
        return (boolean) $result;
282
    }
283
284
    /**
285
     * Return's an array with the names of the expected OK files for the actual subject.
286
     *
287
     * @return array The array with the expected OK filenames
288
     */
289
    protected function getOkFilenames()
290
    {
291
292
        // load the array with the available bunch keys
293
        $bunchKeys = BunchKeys::getAllKeys();
294
295
        // initialize the array for the available okFilenames
296
        $okFilenames = array();
297
298
        // prepare the OK filenames based on the found CSV file information
299
        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...
300
            // intialize the array for the parts of the names (prefix, filename + counter)
301
            $parts = array();
302
            // load the parts from the matches
303
            for ($z = 0; $z < $i; $z++) {
304
                $parts[] = $this->matches[$bunchKeys[$z]];
305
            }
306
307
            // query whether or not, the OK file exists, if yes append it
308
            if (file_exists($okFilename = sprintf('%s/%s.ok', $this->getSourceDir(), implode('_', $parts)))) {
309
                $okFilenames[] = $okFilename;
310
            }
311
        }
312
313
        // prepare and return the pattern for the OK file
314
        return $okFilenames;
315
    }
316
317
    /**
318
     * Query whether or not, the passed CSV filename is in the OK file. If the filename was found,
319
     * it'll be returned and the method return TRUE.
320
     *
321
     * If the filename is NOT in the OK file, the method return's FALSE and the CSV should NOT be
322
     * imported/moved.
323
     *
324
     * @param string $filename The CSV filename to query for
325
     *
326
     * @return void
327
     * @throws \Exception Is thrown, if the passed filename is NOT in the OK file or it can NOT be removed from it
328
     */
329
    protected function removeFromOkFile($filename)
330
    {
331
332
        try {
333
            // load the expected OK filenames
334
            $okFilenames = $this->getOkFilenames();
335
336
            // iterate over the found OK filenames (should usually be only one, but could be more)
337
            foreach ($okFilenames as $okFilename) {
338
                // if the OK filename matches the CSV filename AND the OK file is empty
339
                if (basename($filename, '.csv') === basename($okFilename, '.ok') && filesize($okFilename) === 0) {
340
                    unlink($okFilename);
341
                    return;
342
                }
343
344
                // else, remove the CSV filename from the OK file
345
                $this->removeLineFromFile(basename($filename), $okFilename);
346
                return;
347
            }
348
349
            // throw an exception if either no OK file has been found,
350
            // or the CSV file is not in one of the OK files
351
            throw new \Exception(
352
                sprintf(
353
                    'Can\'t found filename %s in one of the expected OK files: %s',
354
                    $filename,
355
                    implode(', ', $okFilenames)
356
                )
357
            );
358
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
359
        } catch (\Exception $e) {
360
            // wrap and re-throw the exception
361
            throw new \Exception(
362
                sprintf(
363
                    'Can\'t remove filename %s from OK file: %s',
364
                    $filename,
365
                    $okFilename
366
                ),
367
                null,
368
                $e
369
            );
370
        }
371
    }
372
}
373