Completed
Push — 16.x ( 0056bb...bd3602 )
by Tim
02:11 queued 10s
created

OkFileHandler::cleanUpOkFile()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 59

Duplication

Lines 59
Ratio 100 %

Importance

Changes 0
Metric Value
dl 59
loc 59
c 0
b 0
f 0
rs 7.6501
cc 8
nc 14
nop 1

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\Handlers\PidFileHandler
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 2020 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\Handlers;
22
23
use TechDivision\Import\Loaders\FilteredLoaderInterface;
24
use TechDivision\Import\Exceptions\LineNotFoundException;
25
use TechDivision\Import\Exceptions\MissingOkFileException;
26
use TechDivision\Import\Adapter\FilesystemAdapterInterface;
27
use TechDivision\Import\Configuration\Subject\FileResolverConfigurationInterface;
28
29
/**
30
 * An .OK file handler implementation.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2020 TechDivision GmbH <[email protected]>
34
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
35
 * @link      https://github.com/techdivision/import
36
 * @link      http://www.techdivision.com
37
 */
38
class OkFileHandler implements OkFileHandlerInterface
39
{
40
41
    /**
42
     * The loader instance used to load the proposed .OK filenames and it's content.
43
     *
44
     * @var \TechDivision\Import\Loaders\FilteredLoaderInterface
45
     */
46
    private $loader;
47
48
    /**
49
     * The generic file handler instance.
50
     *
51
     * @var \TechDivision\Import\Handlers\GenericFileHandlerInterface
52
     */
53
    private $genericFileHandler;
54
55
    /**
56
     * The filesystem adapter instance.
57
     *
58
     * @var \TechDivision\Import\Adapter\FilesystemAdapterInterface
59
     */
60
    private $filesystemAdapter;
61
62
    /**
63
     * The file resolver configuration instance.
64
     *
65
     * @var \TechDivision\Import\Configuration\Subject\FileResolverConfigurationInterface
66
     */
67
    private $fileResolverConfiguration;
68
69
    /**
70
     * Initializes the file handler instance.
71
     *
72
     * @param \TechDivision\Import\Handlers\GenericFileHandlerInterface|null $genericFileHandler The generic file handler instance
73
     */
74
    public function __construct(GenericFileHandlerInterface $genericFileHandler = null)
75
    {
76
        $this->genericFileHandler = $genericFileHandler ?? new GenericFileHandler();
77
    }
78
79
    /**
80
     * Return's the generic file handler instance.
81
     *
82
     * @return \TechDivision\Import\Handlers\GenericFileHandlerInterface The generic file handler instance
83
     */
84
    protected function getGenericFileHandler()
85
    {
86
        return $this->genericFileHandler;
87
    }
88
89
    /**
90
     * Return's the filesystem adapter instance.
91
     *
92
     * @return \TechDivision\Import\Adapter\FilesystemAdapterInterface The filesystem adapter instance
93
     */
94
    protected function getFilesystemAdapter()
95
    {
96
        return $this->filesystemAdapter;
97
    }
98
99
    /**
100
     * Return's the loader instance used to load the proposed .OK filenames and it's content.
101
     *
102
     * @return \TechDivision\Import\Loaders\FilteredLoaderInterface The loader instance
103
     */
104
    protected function getLoader() : FilteredLoaderInterface
105
    {
106
        return $this->loader;
107
    }
108
109
    /**
110
     * Returns the file resolver configuration instance.
111
     *
112
     * @return \TechDivision\Import\Configuration\Subject\FileResolverConfigurationInterface The configuration instance
113
     */
114
    protected function getFileResolverConfiguration() : FileResolverConfigurationInterface
115
    {
116
        return $this->fileResolverConfiguration;
117
    }
118
119
    /**
120
     * Remove's the passed line from the file with the passed name.
121
     *
122
     * @param string $line     The line to be removed
123
     * @param string $filename The name of the file the line has to be removed
124
     *
125
     * @return void
126
     * @throws \Exception Is thrown, if the file doesn't exists, the line is not found or can not be removed
127
     */
128
    protected function removeLineFromFile(string $line, string $filename) : void
129
    {
130
        $this->getGenericFileHandler()->removeLineFromFile($line, $filename);
0 ignored issues
show
Documentation introduced by
$filename is of type string, but the function expects a resource.

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...
131
    }
132
133
    /**
134
     * Query whether or not the basename, without suffix, of the passed filenames are equal.
135
     *
136
     * @param string $filename1 The first filename to compare
137
     * @param string $filename2 The second filename to compare
138
     *
139
     * @return boolean TRUE if the passed files basename are equal, else FALSE
140
     * @todo Refactorig required, because of duplicate method
141
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::isEqualFilename()
142
     */
143
    protected function isEqualFilename(string $filename1, string $filename2) : bool
144
    {
145
        return $this->stripSuffix($filename1, $this->getSuffix()) === $this->stripSuffix($filename2, $this->getOkFileSuffix());
0 ignored issues
show
Bug introduced by
The method getSuffix() does not seem to exist on object<TechDivision\Impo...Handlers\OkFileHandler>.

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...
Documentation introduced by
$this->getOkFileSuffix() is of type array, 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...
146
    }
147
148
    /**
149
     * Strips the passed suffix, including the (.), from the filename and returns it.
150
     *
151
     * @param string $filename The filename to return the suffix from
152
     * @param string $suffix   The suffix to return
153
     *
154
     * @return string The filname without the suffix
155
     * @todo Refactorig required, because of duplicate method
156
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::stripSuffix()
157
     */
158
    protected function stripSuffix(string $filename, string $suffix) : string
159
    {
160
        return basename($filename, sprintf('.%s', $suffix));
161
    }
162
163
    /**
164
     * Prepares and returns an OK filename from the passed parts.
165
     *
166
     * @param array $parts The parts to concatenate the OK filename from
167
     *
168
     * @return string The OK filename
169
     * @todo Refactorig required, because of duplicate method
170
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::prepareOkFilename()
171
     */
172
    protected function prepareOkFilename(array $parts) : string
173
    {
174
        return sprintf('%s/%s.%s', $this->getSourceDir(), implode($this->getElementSeparator(), $parts), $this->getOkFileSuffix());
175
    }
176
177
    /**
178
     * Returns the delement separator char.
179
     *
180
     *  @return string The element separator char
181
     */
182
    protected function getElementSeparator() : string
183
    {
184
        return $this->getFileResolverConfiguration()->getElementSeparator();
185
    }
186
187
    /**
188
     * Returns the elements the filenames consists of.
189
     *
190
     * @return array The array with the filename elements
191
     * @todo Refactorig required, because of duplicate method
192
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::getPatternElements()
193
     */
194
    protected function getPatternElements() : array
195
    {
196
        return $this->getFileResolverConfiguration()->getPatternElements();
197
    }
198
199
    /**
200
     * Returns the elements the filenames consists of.
201
     *
202
     * @return array The array with the filename elements
203
     * @todo Refactorig required, because of duplicate method
204
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::getOkFileSuffix()
205
     */
206
    protected function getOkFileSuffix() : array
207
    {
208
        return $this->getFileResolverConfiguration()->getOkFileSuffix();
209
    }
210
211
    /**
212
     * Returns the actual source directory to load the files from.
213
     *
214
     * @return string The actual source directory
215
     */
216
    protected function getSourceDir() : string
217
    {
218
        throw new \Exception(sprintf('Method "%s" has not been implemented yet', __METHOD__));
219
    }
220
221
    /**
222
     * Returns the elements the filenames consists of, converted to lowercase.
223
     *
224
     * @return array The array with the filename elements
225
     * @todo Refactorig required, because of duplicate method
226
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::getPatternKeys()
227
     */
228
    protected function getPatternKeys() : array
229
    {
230
231
        // load the pattern keys from the configuration
232
        $patternKeys = $this->getPatternElements();
233
234
        // make sure that they are all lowercase
235
        array_walk($patternKeys, function (&$value) {
236
            $value = strtolower($value);
237
        });
238
239
        // return the pattern keys
240
        return $patternKeys;
241
    }
242
243
    /**
244
     * Return's an array with the names of the expected OK files for the actual subject.
245
     *
246
     * @return array The array with the expected OK filenames
247
     * @todo Refactorig required, because of duplicate method
248
     * @see \TechDivision\Import\Loaders\Filters\OkFileFilter::getOkFilenames()
249
     */
250 View Code Duplication
    protected function getOkFilenames() : array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
251
    {
252
253
        // initialize the array for the available okFilenames
254
        $okFilenames = array();
255
256
        // prepare the OK filenames based on the found CSV file information
257
        for ($i = 1; $i <= sizeof($patternKeys = $this->getPatternKeys()); $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...
258
            // intialize the array for the parts of the names (prefix, filename + counter)
259
            $parts = array();
260
            // load the parts from the matches
261
            for ($z = 0; $z < $i; $z++) {
262
                // append the part
263
                $parts[] = $this->getLoader()->getMatch($patternKeys[$z]);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Load...FilteredLoaderInterface as the method getMatch() does only exist in the following implementations of said interface: TechDivision\Import\Load...PregMatchFilteredLoader.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
264
            }
265
266
            // query whether or not, the OK file exists, if yes append it
267
            if (file_exists($okFilename = $this->prepareOkFilename($parts))) {
268
                $okFilenames[] = $okFilename;
269
            }
270
        }
271
272
        // prepare and return the pattern for the OK file
273
        return $okFilenames;
274
    }
275
276
    /**
277
     * Set's the loader instance used to load the proposed .OK filenames and it's content.
278
     *
279
     * @param \TechDivision\Import\Loaders\FilteredLoaderInterface $loader The loader instance
280
     *
281
     * @return void
282
     */
283
    public function setLoader(FilteredLoaderInterface $loader) : void
284
    {
285
        $this->loader = $loader;
286
    }
287
288
    /**
289
     * Set's the file resolver configuration.
290
     *
291
     * @param \TechDivision\Import\Configuration\Subject\FileResolverConfigurationInterface $fileResolverConfiguration The file resolver configuration
292
     */
293
    public function setFileResolverConfiguration(FileResolverConfigurationInterface $fileResolverConfiguration) : void
294
    {
295
        $this->fileResolverConfiguration = $fileResolverConfiguration;
296
    }
297
298
    /**
299
     * Set's the filesystem adapter instance.
300
     *
301
     * @param \TechDivision\Import\Adapter\FilesystemAdapterInterface $filesystemAdapter The filesystem adapter instance
302
     */
303
    public function setFilesystemAdapter(FilesystemAdapterInterface $filesystemAdapter) : void
304
    {
305
        $this->filesystemAdapter = $filesystemAdapter;
306
    }
307
308
    /**
309
     * Query whether or not, the passed CSV filename is in the OK file. If the filename was found,
310
     * the OK file will be cleaned-up.
311
     *
312
     * @param string $filename The filename to be cleaned-up
313
     *
314
     * @return void
315
     * @throws \Exception Is thrown, if the passed filename is NOT in the OK file or the OK can not be cleaned-up
316
     */
317 View Code Duplication
    public function cleanUpOkFile(string $filename) : void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
318
    {
319
320
        // query whether or not the subject needs an OK file, if yes remove the filename from the file
321
        if ($this->getSubjectConfiguration()->isOkFileNeeded() === false) {
0 ignored issues
show
Bug introduced by
The method getSubjectConfiguration() does not seem to exist on object<TechDivision\Impo...Handlers\OkFileHandler>.

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...
322
            return;
323
        }
324
325
        try {
326
            // try to load the expected OK filenames
327
            if (sizeof($okFilenames = $this->getOkFilenames()) === 0) {
328
                throw new MissingOkFileException(sprintf('Can\'t find a OK filename for file %s', $filename));
329
            }
330
331
            // iterate over the found OK filenames (should usually be only one, but could be more)
332
            foreach ($okFilenames as $okFilename) {
333
                // clear the filecache
334
                \clearstatcache();
335
                // if the OK filename matches the CSV filename AND the OK file is empty
336
                if ($this->isEqualFilename($filename, $okFilename) && filesize($okFilename) === 0) {
337
                    unlink($okFilename);
338
                    return;
339
                }
340
341
                // else, remove the CSV filename from the OK file
342
                $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...
343
                fclose($fh);
344
345
                // if the OK file is empty, delete the file
346
                if (filesize($okFilename) === 0) {
347
                    unlink($okFilename);
348
                }
349
350
                // return immediately
351
                return;
352
            }
353
354
            // throw an exception if either no OK file has been found,
355
            // or the CSV file is not in one of the OK files
356
            throw new \Exception(
357
                sprintf(
358
                    'Can\'t found filename %s in one of the expected OK files: %s',
359
                    $filename,
360
                    implode(', ', $okFilenames)
361
                )
362
            );
363
        } catch (LineNotFoundException $lne) {
364
            // wrap and re-throw the exception
365
            throw new \Exception(
366
                sprintf(
367
                    'Can\'t remove filename %s from OK file: %s',
368
                    $filename,
369
                    $okFilename
370
                ),
371
                null,
372
                $lne
373
            );
374
        }
375
    }
376
377
    /**
378
     * Create's the .OK files for all .CSV files that matches the passed pattern.
379
     *
380
     * @param string $pattern The pattern that matches the .CSV files we want to create the .OK files for
381
     *
382
     * @return int Return's the number of created .OK files
383
     * @throws \Exception Is thrown, one of the proposed .OK files can not be created
384
     */
385
    public function createOkFiles(string $pattern) : int
386
    {
387
388
        // initialize the counter for the processed .OK files
389
        $counter = 0;
390
391
        // load the array with the proposed .OK filenames
392
        $proposedOkFilenames = $this->getLoader()->load($pattern);
0 ignored issues
show
Unused Code introduced by
The call to FilteredLoaderInterface::load() has too many arguments starting with $pattern.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
393
394
        // create the proposed .OK files
395
        foreach ($proposedOkFilenames as $okFilename => $csvFilenames) {
396
            // write the proposed .OK file
397
            if ($this->getFilesystemAdapter()->write($okFilename, implode(PHP_EOL, $csvFilenames)) === false) {
398
                throw new \Exception(sprintf('Can\' create .OK file "%s"', $okFilename));
399
            }
400
            // raise the counter
401
            $counter++;
402
        }
403
404
        // return the number of created .OK files
405
        return $counter;
406
    }
407
}
408