Completed
Push — master ( 8b8afe...5f2bbe )
by Greg
02:21
created

src/Task/Development/PackPhar.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Robo\Task\Development;
3
4
use Robo\Contract\ProgressIndicatorAwareInterface;
5
use Robo\Contract\PrintedInterface;
6
use Robo\Result;
7
use Robo\Task\BaseTask;
8
9
/**
10
 * Creates Phar.
11
 *
12
 * ``` php
13
 * <?php
14
 * $pharTask = $this->taskPackPhar('package/codecept.phar')
15
 *   ->compress()
16
 *   ->stub('package/stub.php');
17
 *
18
 *  $finder = Finder::create()
19
 *      ->name('*.php')
20
 *        ->in('src');
21
 *
22
 *    foreach ($finder as $file) {
23
 *        $pharTask->addFile('src/'.$file->getRelativePathname(), $file->getRealPath());
24
 *    }
25
 *
26
 *    $finder = Finder::create()->files()
27
 *        ->name('*.php')
28
 *        ->in('vendor');
29
 *
30
 *    foreach ($finder as $file) {
31
 *        $pharTask->addStripped('vendor/'.$file->getRelativePathname(), $file->getRealPath());
32
 *    }
33
 *    $pharTask->run();
34
 *
35
 *    // verify Phar is packed correctly
36
 *    $code = $this->_exec('php package/codecept.phar');
37
 * ?>
38
 * ```
39
 */
40
class PackPhar extends BaseTask implements PrintedInterface, ProgressIndicatorAwareInterface
41
{
42
    /**
43
     * @var \Phar
44
     */
45
    protected $phar;
46
47
    /**
48
     * @var null|string
49
     */
50
    protected $compileDir = null;
51
52
    /**
53
     * @var string
54
     */
55
    protected $filename;
56
57
    /**
58
     * @var bool
59
     */
60
    protected $compress = false;
61
62
    protected $stub;
63
64
    protected $bin;
65
66
    /**
67
     * @var string
68
     */
69
    protected $stubTemplate = <<<EOF
70
#!/usr/bin/env php
71
<?php
72
Phar::mapPhar();
73
%s
74
__HALT_COMPILER();
75
EOF;
76
77
    /**
78
     * @var string[]
79
     */
80
    protected $files = [];
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getPrinted()
86
    {
87
        return true;
88
    }
89
90
    /**
91
     * @param string $filename
92
     */
93
    public function __construct($filename)
94
    {
95
        $file = new \SplFileInfo($filename);
96
        $this->filename = $filename;
97
        if (file_exists($file->getRealPath())) {
98
            @unlink($file->getRealPath());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
99
        }
100
        $this->phar = new \Phar($file->getPathname(), 0, $file->getFilename());
101
    }
102
103
    /**
104
     * @param bool $compress
105
     *
106
     * @return $this
107
     */
108
    public function compress($compress = true)
109
    {
110
        $this->compress = $compress;
111
        return $this;
112
    }
113
114
    /**
115
     * @param string $stub
116
     *
117
     * @return $this
118
     */
119
    public function stub($stub)
120
    {
121
        $this->phar->setStub(file_get_contents($stub));
122
        return $this;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function progressIndicatorSteps()
129
    {
130
        // run() will call advanceProgressIndicator() once for each
131
        // file, one after calling stopBuffering, and again after compression.
132
        return count($this->files)+2;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function run()
139
    {
140
        $this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
141
        $this->phar->setSignatureAlgorithm(\Phar::SHA1);
142
        $this->phar->startBuffering();
143
144
        $this->printTaskInfo('Packing {file-count} files into phar', ['file-count' => count($this->files)]);
145
146
        $this->startProgressIndicator();
147
        foreach ($this->files as $path => $content) {
148
            $this->phar->addFromString($path, $content);
149
            $this->advanceProgressIndicator();
150
        }
151
        $this->phar->stopBuffering();
152
        $this->advanceProgressIndicator();
153
154
        if ($this->compress and in_array('GZ', \Phar::getSupportedCompression())) {
155
            if (count($this->files) > 1000) {
156
                $this->printTaskInfo('Too many files. Compression DISABLED');
157
            } else {
158
                $this->printTaskInfo('{filename} compressed', ['filename' => $this->filename]);
159
                $this->phar = $this->phar->compressFiles(\Phar::GZ);
160
            }
161
        }
162
        $this->advanceProgressIndicator();
163
        $this->stopProgressIndicator();
164
        $this->printTaskSuccess('{filename} produced', ['filename' => $this->filename]);
165
        return Result::success($this, '', ['time' => $this->getExecutionTime()]);
166
    }
167
168
    /**
169
     * @param string $path
170
     * @param string $file
171
     *
172
     * @return $this
173
     */
174
    public function addStripped($path, $file)
175
    {
176
        $this->files[$path] = $this->stripWhitespace(file_get_contents($file));
177
        return $this;
178
    }
179
180
    /**
181
     * @param string $path
182
     * @param string $file
183
     *
184
     * @return $this
185
     */
186
    public function addFile($path, $file)
187
    {
188
        $this->files[$path] = file_get_contents($file);
189
        return $this;
190
    }
191
192
    /**
193
     * @param \Symfony\Component\Finder\SplFileInfo[] $files
194
     */
195
    public function addFiles($files)
196
    {
197
        foreach ($files as $file) {
198
            $this->addFile($file->getRelativePathname(), $file->getRealPath());
199
        }
200
    }
201
202
    /**
203
     * @param string $file
204
     *
205
     * @return $this
206
     */
207
    public function executable($file)
208
    {
209
        $source = file_get_contents($file);
210
        if (strpos($source, '#!/usr/bin/env php') === 0) {
211
            $source = substr($source, strpos($source, '<?php') + 5);
212
        }
213
        $this->phar->setStub(sprintf($this->stubTemplate, $source));
214
        return $this;
215
    }
216
217
    /**
218
     * Strips whitespace from source. Taken from composer
219
     *
220
     * @param string $source
221
     *
222
     * @return string
223
     */
224
    private function stripWhitespace($source)
225
    {
226
        if (!function_exists('token_get_all')) {
227
            return $source;
228
        }
229
230
        $output = '';
231
        foreach (token_get_all($source) as $token) {
232
            if (is_string($token)) {
233
                $output .= $token;
234
            } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
235
                // $output .= $token[1];
236
                $output .= str_repeat("\n", substr_count($token[1], "\n"));
237
            } elseif (T_WHITESPACE === $token[0]) {
238
                // reduce wide spaces
239
                $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
240
                // normalize newlines to \n
241
                $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
242
                // trim leading spaces
243
                $whitespace = preg_replace('{\n +}', "\n", $whitespace);
244
                $output .= $whitespace;
245
            } else {
246
                $output .= $token[1];
247
            }
248
        }
249
250
        return $output;
251
    }
252
}
253