Completed
Push — develop ( 21139f...eaa140 )
by Tom
12s
created

PatchedPharPackageTask   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 479
Duplicated Lines 2.09 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 59
lcom 1
cbo 0
dl 10
loc 479
rs 6.1904
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A createMetadata() 0 4 1
A createFileSet() 0 7 1
B setSignature() 0 25 6
A setCompression() 0 16 3
A getCompressionLabel() 0 15 3
A setDestFile() 0 4 1
A setBaseDir() 0 4 1
A setCliStub() 0 4 1
A setWebStub() 0 4 1
A setStub() 0 4 1
A setAlias() 0 4 1
A setKey() 0 4 1
A setKeyPassword() 0 4 1
B main() 0 26 3
C checkPreconditions() 10 47 13
B buildPhar() 0 55 8
A initPhar() 0 12 2
B compressEachFile() 0 24 4
C compressAllFiles() 0 43 7

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PatchedPharPackageTask often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PatchedPharPackageTask, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * @author Tom Klingenberg <https://github.com/ktomk>
4
 * @license LGPL-3.0 <https://spdx.org/licenses/LGPL-3.0.html>
5
 *
6
 * This software consists of voluntary contributions made by many individuals
7
 * and is licensed under the LGPL. For more information please see
8
 * <http://phing.info>.
9
 */
10
11
/**
12
 * Package task for {@link http://www.php.net/manual/en/book.phar.php Phar technology}.
13
 *
14
 * @package phing.tasks.ext
15
 * @author Alexey Shockov <[email protected]>
16
 * @since 2.4.0
17
 * @see PharPackageTask
18
 */
19
class PatchedPharPackageTask
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
20
    extends MatchingTask
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
21
{
22
    /**
23
     * @var PhingFile
24
     */
25
    private $destinationFile;
26
27
    /**
28
     * @var int
29
     */
30
    private $compression = Phar::NONE;
31
32
    /**
33
     * Base directory, from where local package paths will be calculated.
34
     *
35
     * @var PhingFile
36
     */
37
    private $baseDirectory;
38
39
    /**
40
     * @var PhingFile
41
     */
42
    private $cliStubFile;
43
44
    /**
45
     * @var PhingFile
46
     */
47
    private $webStubFile;
48
49
    /**
50
     * @var string
51
     */
52
    private $stubPath;
53
54
    /**
55
     * Private key the Phar will be signed with.
56
     *
57
     * @var PhingFile
58
     */
59
    private $key;
60
61
    /**
62
     * Password for the private key.
63
     *
64
     * @var string
65
     */
66
    private $keyPassword = '';
67
68
    /**
69
     * @var int
70
     */
71
    private $signatureAlgorithm = Phar::SHA1;
72
73
    /**
74
     * @var array
75
     */
76
    private $filesets = array();
77
78
    /**
79
     * @var PharMetadata
80
     */
81
    private $metadata = null;
82
83
    /**
84
     * @var string
85
     */
86
    private $alias;
87
88
    /**
89
     * @return PharMetadata
90
     */
91
    public function createMetadata()
92
    {
93
        return ($this->metadata = new PharMetadata());
94
    }
95
96
    /**
97
     * @return FileSet
98
     */
99
    public function createFileSet()
100
    {
101
        $this->fileset    = new IterableFileSet();
102
        $this->filesets[] = $this->fileset;
103
104
        return $this->fileset;
105
    }
106
107
    /**
108
     * Signature algorithm (md5, sha1, sha256, sha512, openssl),
109
     * used for this package.
110
     *
111
     * @param string $algorithm
112
     */
113
    public function setSignature($algorithm)
114
    {
115
        /*
116
         * If we don't support passed algprithm, leave old one.
117
         */
118
        switch ($algorithm) {
119
            case 'md5':
120
                $this->signatureAlgorithm = Phar::MD5;
121
                break;
122
            case 'sha1':
123
                $this->signatureAlgorithm = Phar::SHA1;
124
                break;
125
            case 'sha256':
126
                $this->signatureAlgorithm = Phar::SHA256;
127
                break;
128
            case 'sha512':
129
                $this->signatureAlgorithm = Phar::SHA512;
130
                break;
131
            case 'openssl':
132
                $this->signatureAlgorithm = Phar::OPENSSL;
133
                break;
134
            default:
135
                break;
136
        }
137
    }
138
139
    /**
140
     * Compression type (gzip, bzip2, none) to apply to the packed files.
141
     *
142
     * @param string $compression
143
     */
144
    public function setCompression($compression)
145
    {
146
        /**
147
         * If we don't support passed compression, leave old one.
148
         */
149
        switch ($compression) {
150
            case 'gzip':
151
                $this->compression = Phar::GZ;
152
                break;
153
            case 'bzip2':
154
                $this->compression = Phar::BZ2;
155
                break;
156
            default:
157
                break;
158
        }
159
    }
160
161
    /**
162
     * @return string
163
     */
164
    private function getCompressionLabel()
165
    {
166
        $compression = $this->compression;
167
168
        switch ($compression) {
169
            case Phar::GZ:
170
                return "gzip";
171
172
            case Phar::BZ2:
173
                return "bzip2";
174
175
            default:
176
                return sprintf("int(%d)", $compression);
177
        }
178
    }
179
180
    /**
181
     * Destination (output) file.
182
     *
183
     * @param PhingFile $destinationFile
184
     */
185
    public function setDestFile(PhingFile $destinationFile)
186
    {
187
        $this->destinationFile = $destinationFile;
188
    }
189
190
    /**
191
     * Base directory, which will be deleted from each included file (from path).
192
     * Paths with deleted basedir part are local paths in package.
193
     *
194
     * @param PhingFile $baseDirectory
195
     */
196
    public function setBaseDir(PhingFile $baseDirectory)
197
    {
198
        $this->baseDirectory = $baseDirectory;
199
    }
200
201
    /**
202
     * Relative path within the phar package to run,
203
     * if accessed on the command line.
204
     *
205
     * @param PhingFile $stubFile
206
     */
207
    public function setCliStub(PhingFile $stubFile)
208
    {
209
        $this->cliStubFile = $stubFile;
210
    }
211
212
    /**
213
     * Relative path within the phar package to run,
214
     * if accessed through a web browser.
215
     *
216
     * @param PhingFile $stubFile
217
     */
218
    public function setWebStub(PhingFile $stubFile)
219
    {
220
        $this->webStubFile = $stubFile;
221
    }
222
223
    /**
224
     * A path to a php file that contains a custom stub.
225
     *
226
     * @param string $stubPath
227
     */
228
    public function setStub($stubPath)
229
    {
230
        $this->stubPath = $stubPath;
231
    }
232
233
    /**
234
     * An alias to assign to the phar package.
235
     *
236
     * @param string $alias
237
     */
238
    public function setAlias($alias)
239
    {
240
        $this->alias = $alias;
241
    }
242
243
    /**
244
     * Sets the private key to use to sign the Phar with.
245
     *
246
     * @param PhingFile $key Private key to sign the Phar with.
247
     */
248
    public function setKey(PhingFile $key)
249
    {
250
        $this->key = $key;
251
    }
252
253
    /**
254
     * Password for the private key.
255
     *
256
     * @param string $keyPassword
257
     */
258
    public function setKeyPassword($keyPassword)
259
    {
260
        $this->keyPassword = $keyPassword;
261
    }
262
263
    /**
264
     * @throws BuildException
265
     */
266
    public function main()
267
    {
268
        $this->checkPreconditions();
269
270
        try {
271
            $this->log(
272
                'Building package: ' . $this->destinationFile->__toString(),
273
                Project::MSG_INFO
274
            );
275
276
            $baseDirectory = realpath($this->baseDirectory->getPath());
277
278
            try {
279
                $this->compressAllFiles($this->initPhar(), $baseDirectory);
280
            } catch (\RuntimeException $e) {
281
                $this->log('Most likely compression failed (known bug): ' . $e->getMessage());
282
                $this->compressEachFile($this->initPhar(), $baseDirectory);
283
            }
284
        } catch (Exception $e) {
285
            throw new BuildException(
286
                'Problem creating package: ' . $e->getMessage(),
287
                $e,
288
                $this->getLocation()
289
            );
290
        }
291
    }
292
293
    /**
294
     * @throws BuildException
295
     */
296
    private function checkPreconditions()
297
    {
298
        if (!extension_loaded('phar')) {
299
            throw new BuildException(
300
                "PharPackageTask require either PHP 5.3 or better or the PECL's Phar extension"
301
            );
302
        }
303
304
        if (is_null($this->destinationFile)) {
305
            throw new BuildException("destfile attribute must be set!", $this->getLocation());
306
        }
307
308
        if ($this->destinationFile->exists() && $this->destinationFile->isDirectory()) {
309
            throw new BuildException("destfile is a directory!", $this->getLocation());
310
        }
311
312
        if (!$this->destinationFile->canWrite()) {
313
            throw new BuildException("Can not write to the specified destfile!", $this->getLocation());
314
        }
315 View Code Duplication
        if (!is_null($this->baseDirectory)) {
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...
316
            if (!$this->baseDirectory->exists()) {
317
                throw new BuildException(
318
                    "basedir '" . (string) $this->baseDirectory . "' does not exist!", $this->getLocation()
319
                );
320
            }
321
        }
322
        if ($this->signatureAlgorithm == Phar::OPENSSL) {
323
324
            if (!extension_loaded('openssl')) {
325
                throw new BuildException(
326
                    "PHP OpenSSL extension is required for OpenSSL signing of Phars!", $this->getLocation()
327
                );
328
            }
329
330
            if (is_null($this->key)) {
331
                throw new BuildException("key attribute must be set for OpenSSL signing!", $this->getLocation());
332
            }
333
334 View Code Duplication
            if (!$this->key->exists()) {
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...
335
                throw new BuildException("key '" . (string) $this->key . "' does not exist!", $this->getLocation());
336
            }
337
338
            if (!$this->key->canRead()) {
339
                throw new BuildException("key '" . (string) $this->key . "' cannot be read!", $this->getLocation());
340
            }
341
        }
342
    }
343
344
    /**
345
     * Build and configure Phar object.
346
     *
347
     * @return Phar
348
     */
349
    private function buildPhar()
350
    {
351
        $phar = new Phar($this->destinationFile);
352
353
        if ($this->signatureAlgorithm == Phar::OPENSSL) {
354
355
            // Load up the contents of the key
356
            $keyContents = file_get_contents($this->key);
357
358
            // Setup an OpenSSL resource using the private key and tell the Phar
359
            // to sign it using that key.
360
            $private = openssl_pkey_get_private($keyContents, $this->keyPassword);
361
            $phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
362
363
            // Get the details so we can get the public key and write that out
364
            // alongside the phar.
365
            $details = openssl_pkey_get_details($private);
366
            file_put_contents($this->destinationFile . '.pubkey', $details['key']);
367
368
        } else {
369
            $phar->setSignatureAlgorithm($this->signatureAlgorithm);
370
        }
371
372
        if (!empty($this->stubPath)) {
373
            $phar->setStub(file_get_contents($this->stubPath));
374
        } else {
375
            if (!empty($this->cliStubFile)) {
376
                $cliStubFile = $this->cliStubFile->getPathWithoutBase($this->baseDirectory);
377
            } else {
378
                $cliStubFile = null;
379
            }
380
381
            if (!empty($this->webStubFile)) {
382
                $webStubFile = $this->webStubFile->getPathWithoutBase($this->baseDirectory);
383
            } else {
384
                $webStubFile = null;
385
            }
386
387
            $phar->setDefaultStub($cliStubFile, $webStubFile);
388
        }
389
390
        if ($this->metadata === null) {
391
            $this->createMetaData();
392
        }
393
394
        if ($metadata = $this->metadata->toArray()) {
395
            $phar->setMetadata($metadata);
396
        }
397
398
        if (!empty($this->alias)) {
399
            $phar->setAlias($this->alias);
400
        }
401
402
        return $phar;
403
    }
404
405
    /**
406
     * @return Phar
407
     */
408
    private function initPhar()
409
    {
410
        /**
411
         * Delete old package, if exists.
412
         */
413
        if ($this->destinationFile->exists()) {
414
            $this->destinationFile->delete();
415
        }
416
        $phar = $this->buildPhar();
417
418
        return $phar;
419
    }
420
421
    /**
422
     * @param Phar   $phar
423
     * @param string $baseDirectory
424
     */
425
    private function compressEachFile(Phar $phar, $baseDirectory)
426
    {
427
        $phar->startBuffering();
428
429
        foreach ($this->filesets as $fileset) {
430
            $this->log(
431
                'Adding specified files in ' . $fileset->getDir($this->project) . ' to package',
432
                Project::MSG_VERBOSE
433
            );
434
435
            if (Phar::NONE != $this->compression) {
436
                foreach ($fileset as $file) {
437
                    $localName = substr($file, strlen($baseDirectory) + 1);
438
                    $this->log($localName . "... ", Project::MSG_VERBOSE);
439
                    $phar->addFile($file, $localName);
440
                    $phar[$localName]->compress($this->compression);
441
                }
442
            } else {
443
                $phar->buildFromIterator($fileset, $baseDirectory);
444
            }
445
        }
446
447
        $phar->stopBuffering();
448
    }
449
450
    /**
451
     * @param Phar   $phar
452
     * @param string $baseDirectory
453
     */
454
    private function compressAllFiles(Phar $phar, $baseDirectory)
455
    {
456
        $total = 0;
457
458
        $phar->startBuffering();
459
460
        foreach ($this->filesets as $fileset) {
461
            $dir = $fileset->getDir($this->project);
462
            $msg = sprintf("Fileset %s ...", $dir);
463
            $this->log($msg, Project::MSG_VERBOSE);
464
            $added = $phar->buildFromIterator($fileset, $baseDirectory);
465
            $total += count($added);
466
        }
467
468
        $phar->stopBuffering();
469
470
        if (Phar::NONE === $this->compression) {
471
            return;
472
        }
473
474
        $msg = sprintf("Compressing %d files (compression: %s) ... ", $total, $this->getCompressionLabel());
475
        $this->log($msg, Project::MSG_VERBOSE);
476
477
        // safeguard open files soft limit
478
        if (function_exists('posix_getrlimit')) {
479
            $rlimit = posix_getrlimit();
480
            if ($rlimit['soft openfiles'] < ($total + 5)) {
481
                $msg = sprintf("Limit of openfiles (%d) is too low.", $rlimit['soft openfiles']);
482
                $this->log($msg, Project::MSG_VERBOSE);
483
            }
484
        }
485
486
        // safeguard compression
487
        try {
488
            $phar->compressFiles($this->compression);
489
        } catch (BadMethodCallException $e) {
490
            if ($e->getMessage() === 'unable to create temporary file') {
491
                $msg = sprintf("Info: Check openfiles limit it must be %d or higher", $total + 5);
492
                throw new BadMethodCallException($msg, 0, $e);
493
            }
494
            throw $e;
495
        }
496
    }
497
}
498