Completed
Pull Request — master (#41)
by Tim
03:27
created

SubjectPlugin   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 324
Duplicated Lines 0.93 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 17.32%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 24
c 3
b 0
f 0
lcom 1
cbo 8
dl 3
loc 324
ccs 22
cts 127
cp 0.1732
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
B process() 0 46 4
B processSubject() 3 68 6
B subjectFactory() 0 27 2
B isPartOfBunch() 0 40 3
B getOkFilenames() 0 27 4
B removeFromOkFile() 0 43 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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