Completed
Push — filehelper-unlink ( 5d3071 )
by Alexander
10:05
created

DevController::unlink()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\build\controllers;
9
10
use Yii;
11
use yii\base\InvalidParamException;
12
use yii\console\Controller;
13
use yii\helpers\Console;
14
use yii\helpers\FileHelper;
15
16
/**
17
 * This command helps to set up a dev environment with all extensions and applications.
18
 *
19
 * It will clone an extension or app repo and link the yii2 dev installation to the containted applications/extensions vendor dirs
20
 * to help working on yii using the application to test it.
21
 *
22
 * @author Carsten Brandt <[email protected]>
23
 * @since 2.0
24
 */
25
class DevController extends Controller
26
{
27
    public $defaultAction = 'all';
28
29
    /**
30
     * @var bool whether to use HTTP when cloning github repositories
31
     */
32
    public $useHttp = false;
33
34
    public $apps = [
35
        'basic' => '[email protected]:yiisoft/yii2-app-basic.git',
36
        'advanced' => '[email protected]:yiisoft/yii2-app-advanced.git',
37
        'benchmark' => '[email protected]:yiisoft/yii2-app-benchmark.git',
38
    ];
39
40
    public $extensions = [
41
        'apidoc' => '[email protected]:yiisoft/yii2-apidoc.git',
42
        'authclient' => '[email protected]:yiisoft/yii2-authclient.git',
43
        'bootstrap' => '[email protected]:yiisoft/yii2-bootstrap.git',
44
        'codeception' => '[email protected]:yiisoft/yii2-codeception.git',
45
        'composer' => '[email protected]:yiisoft/yii2-composer.git',
46
        'debug' => '[email protected]:yiisoft/yii2-debug.git',
47
        'elasticsearch' => '[email protected]:yiisoft/yii2-elasticsearch.git',
48
        'faker' => '[email protected]:yiisoft/yii2-faker.git',
49
        'gii' => '[email protected]:yiisoft/yii2-gii.git',
50
        'httpclient' => '[email protected]:yiisoft/yii2-httpclient.git',
51
        'imagine' => '[email protected]:yiisoft/yii2-imagine.git',
52
        'jui' => '[email protected]:yiisoft/yii2-jui.git',
53
        'mongodb' => '[email protected]:yiisoft/yii2-mongodb.git',
54
        'queue' => '[email protected]:yiisoft/yii2-queue.git',
55
        'redis' => '[email protected]:yiisoft/yii2-redis.git',
56
        'shell' => '[email protected]:yiisoft/yii2-shell.git',
57
        'smarty' => '[email protected]:yiisoft/yii2-smarty.git',
58
        'sphinx' => '[email protected]:yiisoft/yii2-sphinx.git',
59
        'swiftmailer' => '[email protected]:yiisoft/yii2-swiftmailer.git',
60
        'twig' => '[email protected]:yiisoft/yii2-twig.git',
61
    ];
62
63
64
    /**
65
     * Install all extensions and advanced + basic app.
66
     */
67
    public function actionAll()
68
    {
69
        if (!$this->confirm('Install all applications and all extensions now?')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirm('Install ...d all extensions now?') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
70
            return 1;
71
        }
72
73
        foreach ($this->extensions as $ext => $repo) {
74
            $ret = $this->actionExt($ext);
75
            if ($ret !== 0) {
76
                return $ret;
77
            }
78
        }
79
80
        foreach ($this->apps as $app => $repo) {
81
            $ret = $this->actionApp($app);
82
            if ($ret !== 0) {
83
                return $ret;
84
            }
85
        }
86
87
        return 0;
88
    }
89
90
    /**
91
     * Runs a command in all extension and application directories.
92
     *
93
     * Can be used to run e.g. `git pull`.
94
     *
95
     *     ./build/build dev/run git pull
96
     *
97
     * @param string $command the command to run
98
     */
99
    public function actionRun($command)
0 ignored issues
show
Unused Code introduced by
The parameter $command is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
    {
101
        $command = implode(' ', func_get_args());
102
103
        // root of the dev repo
104
        $base = dirname(dirname(__DIR__));
105
        $dirs = $this->listSubDirs("$base/extensions");
106
        $dirs = array_merge($dirs, $this->listSubDirs("$base/apps"));
107
        asort($dirs);
108
109
        $oldcwd = getcwd();
110
        foreach ($dirs as $dir) {
111
            $displayDir = substr($dir, strlen($base));
112
            $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD);
113
            chdir($dir);
114
            passthru($command);
115
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
116
        }
117
        chdir($oldcwd);
118
    }
119
120
    /**
121
     * This command installs a project template in the `apps` directory and links the framework and extensions.
122
     *
123
     * It basically runs the following commands in the dev repo root:
124
     *
125
     * - Run `composer update`
126
     * - `rm -rf apps/basic/vendor/yiisoft/yii2`
127
     * - `rm -rf apps/basic/vendor/yiisoft/yii2-*`
128
     *
129
     * And replaces them with symbolic links to the extensions and framework path in the dev repo.
130
     *
131
     * Extensions required by the application are automatically installed using the `ext` action.
132
     *
133
     * @param string $app the application name e.g. `basic` or `advanced`.
134
     * @param string $repo url of the git repo to clone if it does not already exist.
135
     * @return int return code
136
     */
137
    public function actionApp($app, $repo = null)
138
    {
139
        // root of the dev repo
140
        $base = dirname(dirname(__DIR__));
141
        $appDir = "$base/apps/$app";
142
143
        if (!file_exists($appDir)) {
144
            if (empty($repo)) {
145
                if (isset($this->apps[$app])) {
146
                    $repo = $this->apps[$app];
147
                    if ($this->useHttp) {
148
                        $repo = str_replace('[email protected]:', 'https://github.com/', $repo);
149
                    }
150
                } else {
151
                    $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED);
152
                    return 1;
153
                }
154
            }
155
156
            $this->stdout("cloning application repo '$app' from '$repo'...\n", Console::BOLD);
157
            passthru('git clone ' . escapeshellarg($repo) . ' ' . $appDir);
158
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
159
        }
160
161
        // cleanup
162
        $this->stdout("cleaning up application '$app' vendor directory...\n", Console::BOLD);
163
        $this->cleanupVendorDir($appDir);
164
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
165
166
        // composer update
167
        $this->stdout("updating composer for app '$app'...\n", Console::BOLD);
168
        chdir($appDir);
169
        passthru('composer update --prefer-dist');
170
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
171
172
        // link directories
173
        $this->stdout("linking framework and extensions to '$app' app vendor dir...\n", Console::BOLD);
174
        $this->linkFrameworkAndExtensions($appDir, $base);
175
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
176
177
        return 0;
178
    }
179
180
    /**
181
     * This command installs an extension in the `extensions` directory and links the framework and other extensions.
182
     *
183
     * @param string $extension the application name e.g. `basic` or `advanced`.
184
     * @param string $repo url of the git repo to clone if it does not already exist.
185
     *
186
     * @return int
187
     */
188
    public function actionExt($extension, $repo = null)
189
    {
190
        // root of the dev repo
191
        $base = dirname(dirname(__DIR__));
192
        $extensionDir = "$base/extensions/$extension";
193
194
        if (!file_exists($extensionDir)) {
195
            if (empty($repo)) {
196
                if (isset($this->extensions[$extension])) {
197
                    $repo = $this->extensions[$extension];
198
                    if ($this->useHttp) {
199
                        $repo = str_replace('[email protected]:', 'https://github.com/', $repo);
200
                    }
201
                } else {
202
                    $this->stderr("Repo argument is required for extension '$extension'.\n", Console::FG_RED);
203
                    return 1;
204
                }
205
            }
206
207
            $this->stdout("cloning extension repo '$extension' from '$repo'...\n", Console::BOLD);
208
            passthru('git clone ' . escapeshellarg($repo) . ' ' . $extensionDir);
209
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
210
        }
211
212
        // cleanup
213
        $this->stdout("cleaning up extension '$extension' vendor directory...\n", Console::BOLD);
214
        $this->cleanupVendorDir($extensionDir);
215
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
216
217
        // composer update
218
        $this->stdout("updating composer for extension '$extension'...\n", Console::BOLD);
219
        chdir($extensionDir);
220
        passthru('composer update --prefer-dist');
221
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
222
223
        // link directories
224
        $this->stdout("linking framework and extensions to '$extension' vendor dir...\n", Console::BOLD);
225
        $this->linkFrameworkAndExtensions($extensionDir, $base);
226
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
227
228
        return 0;
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234
    public function options($actionID)
235
    {
236
        $options = parent::options($actionID);
237
        if (in_array($actionID, ['ext', 'app', 'all'], true)) {
238
            $options[] = 'useHttp';
239
        }
240
241
        return $options;
242
    }
243
244
245
    /**
246
     * Remove all symlinks in the vendor subdirectory of the directory specified.
247
     * @param string $dir base directory
248
     */
249
    protected function cleanupVendorDir($dir)
250
    {
251
        if (is_link($link = "$dir/vendor/yiisoft/yii2")) {
252
            $this->stdout("Removing symlink $link.\n");
253
            FileHelper::unlink($link);
254
        }
255
        $extensions = $this->findDirs("$dir/vendor/yiisoft");
256
        foreach ($extensions as $ext) {
257
            if (is_link($link = "$dir/vendor/yiisoft/yii2-$ext")) {
258
                $this->stdout("Removing symlink $link.\n");
259
                FileHelper::unlink($link);
260
            }
261
        }
262
    }
263
264
    /**
265
     * Creates symlinks to framework and extension sources for the application.
266
     * @param string $dir application directory
267
     * @param string $base Yii sources base directory
268
     *
269
     * @return int
270
     */
271
    protected function linkFrameworkAndExtensions($dir, $base)
272
    {
273
        if (is_dir($link = "$dir/vendor/yiisoft/yii2")) {
274
            $this->stdout("Removing dir $link.\n");
275
            FileHelper::removeDirectory($link);
276
            $this->stdout("Creating symlink for $link.\n");
277
            symlink("$base/framework", $link);
278
        }
279
        $extensions = $this->findDirs("$dir/vendor/yiisoft");
280
        foreach ($extensions as $ext) {
281
            if (is_dir($link = "$dir/vendor/yiisoft/yii2-$ext")) {
282
                $this->stdout("Removing dir $link.\n");
283
                FileHelper::removeDirectory($link);
284
                $this->stdout("Creating symlink for $link.\n");
285
                if (!file_exists("$base/extensions/$ext")) {
286
                    $ret = $this->actionExt($ext);
287
                    if ($ret !== 0) {
288
                        return $ret;
289
                    }
290
                }
291
                symlink("$base/extensions/$ext", $link);
292
            }
293
        }
294
    }
295
296
    /**
297
     * Get a list of subdirectories for directory specified.
298
     * @param string $dir directory to read
299
     *
300
     * @return array list of subdirectories
301
     */
302
    protected function listSubDirs($dir)
303
    {
304
        $list = [];
305
        $handle = opendir($dir);
306
        if ($handle === false) {
307
            throw new InvalidParamException("Unable to open directory: $dir");
308
        }
309
        while (($file = readdir($handle)) !== false) {
310
            if ($file === '.' || $file === '..') {
311
                continue;
312
            }
313
            // ignore hidden directories
314
            if ($file[0] === '.') {
315
                continue;
316
            }
317
            if (is_dir("$dir/$file")) {
318
                $list[] = "$dir/$file";
319
            }
320
        }
321
        closedir($handle);
322
        return $list;
323
    }
324
325
    /**
326
     * Finds linkable applications.
327
     *
328
     * @param string $dir directory to search in
329
     * @return array list of applications command can link
330
     */
331
    protected function findDirs($dir)
332
    {
333
        $list = [];
334
        $handle = @opendir($dir);
335
        if ($handle === false) {
336
            return [];
337
        }
338
        while (($file = readdir($handle)) !== false) {
339
            if ($file === '.' || $file === '..') {
340
                continue;
341
            }
342
            $path = $dir . DIRECTORY_SEPARATOR . $file;
343
            if (is_dir($path) && preg_match('/^yii2-(.*)$/', $file, $matches)) {
344
                $list[] = $matches[1];
345
            }
346
        }
347
        closedir($handle);
348
349
        foreach ($list as $i => $e) {
350
            if ($e === 'composer') { // skip composer to not break composer update
351
                unset($list[$i]);
352
            }
353
        }
354
355
        return $list;
356
    }
357
}
358