Passed
Push — scrutinizer-migrate-to-new-eng... ( 58afd6 )
by Alexander
18:11
created

DevController   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 3
dl 0
loc 352
rs 8.5599
c 0
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like DevController 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 DevController, and based on these observations, apply Extract Interface, too.

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
    /**
28
     * {@inheritdoc}
29
     */
30
    public $defaultAction = 'all';
31
    /**
32
     * @var bool whether to use HTTP when cloning github repositories
33
     */
34
    public $useHttp = false;
35
    /**
36
     * @var bool whether to use --no-progress option when running composer
37
     */
38
    public $composerNoProgress = false;
39
    /**
40
     * @var array
41
     */
42
    public $apps = [
43
        'basic' => '[email protected]:yiisoft/yii2-app-basic.git',
44
        'advanced' => '[email protected]:yiisoft/yii2-app-advanced.git',
45
        'benchmark' => '[email protected]:yiisoft/yii2-app-benchmark.git',
46
    ];
47
    /**
48
     * @var array
49
     */
50
    public $extensions = [
51
        'apidoc' => '[email protected]:yiisoft/yii2-apidoc.git',
52
        'authclient' => '[email protected]:yiisoft/yii2-authclient.git',
53
        'bootstrap' => '[email protected]:yiisoft/yii2-bootstrap.git',
54
        'codeception' => '[email protected]:yiisoft/yii2-codeception.git',
55
        'composer' => '[email protected]:yiisoft/yii2-composer.git',
56
        'debug' => '[email protected]:yiisoft/yii2-debug.git',
57
        'elasticsearch' => '[email protected]:yiisoft/yii2-elasticsearch.git',
58
        'faker' => '[email protected]:yiisoft/yii2-faker.git',
59
        'gii' => '[email protected]:yiisoft/yii2-gii.git',
60
        'httpclient' => '[email protected]:yiisoft/yii2-httpclient.git',
61
        'imagine' => '[email protected]:yiisoft/yii2-imagine.git',
62
        'jui' => '[email protected]:yiisoft/yii2-jui.git',
63
        'mongodb' => '[email protected]:yiisoft/yii2-mongodb.git',
64
        'queue' => '[email protected]:yiisoft/yii2-queue.git',
65
        'redis' => '[email protected]:yiisoft/yii2-redis.git',
66
        'shell' => '[email protected]:yiisoft/yii2-shell.git',
67
        'smarty' => '[email protected]:yiisoft/yii2-smarty.git',
68
        'sphinx' => '[email protected]:yiisoft/yii2-sphinx.git',
69
        'swiftmailer' => '[email protected]:yiisoft/yii2-swiftmailer.git',
70
        'twig' => '[email protected]:yiisoft/yii2-twig.git',
71
    ];
72
73
74
    /**
75
     * Install all extensions and advanced + basic app.
76
     */
77
    public function actionAll()
78
    {
79
        if (!$this->confirm('Install all applications and all extensions now?')) {
80
            return 1;
81
        }
82
83
        foreach ($this->extensions as $ext => $repo) {
84
            $ret = $this->actionExt($ext);
85
            if ($ret !== 0) {
86
                return $ret;
87
            }
88
        }
89
90
        foreach ($this->apps as $app => $repo) {
91
            $ret = $this->actionApp($app);
92
            if ($ret !== 0) {
93
                return $ret;
94
            }
95
        }
96
97
        return 0;
98
    }
99
100
    /**
101
     * Runs a command in all extension and application directories.
102
     *
103
     * Can be used to run e.g. `git pull`.
104
     *
105
     *     ./build/build dev/run git pull
106
     *
107
     * @param string $command the command to run
108
     */
109
    public function actionRun($command)
110
    {
111
        $command = implode(' ', \func_get_args());
112
113
        // root of the dev repo
114
        $base = \dirname(\dirname(__DIR__));
115
        $dirs = $this->listSubDirs("$base/extensions");
116
        $dirs = array_merge($dirs, $this->listSubDirs("$base/apps"));
117
        asort($dirs);
118
119
        $oldcwd = getcwd();
120
        foreach ($dirs as $dir) {
121
            $displayDir = substr($dir, \strlen($base));
122
            $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD);
123
            chdir($dir);
124
            passthru($command);
125
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
126
        }
127
        chdir($oldcwd);
128
    }
129
130
    /**
131
     * This command installs a project template in the `apps` directory and links the framework and extensions.
132
     *
133
     * It basically runs the following commands in the dev repo root:
134
     *
135
     * - Run `composer update`
136
     * - `rm -rf apps/basic/vendor/yiisoft/yii2`
137
     * - `rm -rf apps/basic/vendor/yiisoft/yii2-*`
138
     *
139
     * And replaces them with symbolic links to the extensions and framework path in the dev repo.
140
     *
141
     * Extensions required by the application are automatically installed using the `ext` action.
142
     *
143
     * @param string $app the application name e.g. `basic` or `advanced`.
144
     * @param string $repo url of the git repo to clone if it does not already exist.
145
     * @return int return code
146
     */
147
    public function actionApp($app, $repo = null)
148
    {
149
        // root of the dev repo
150
        $base = \dirname(\dirname(__DIR__));
151
        $appDir = "$base/apps/$app";
152
153
        if (!file_exists($appDir)) {
154
            if (empty($repo)) {
155
                if (isset($this->apps[$app])) {
156
                    $repo = $this->apps[$app];
157
                    if ($this->useHttp) {
158
                        $repo = str_replace('[email protected]:', 'https://github.com/', $repo);
159
                    }
160
                } else {
161
                    $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED);
162
                    return 1;
163
                }
164
            }
165
166
            $this->stdout("cloning application repo '$app' from '$repo'...\n", Console::BOLD);
167
            passthru('git clone ' . escapeshellarg($repo) . ' ' . $appDir);
168
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
169
        }
170
171
        // cleanup
172
        $this->stdout("cleaning up application '$app' vendor directory...\n", Console::BOLD);
173
        $this->cleanupVendorDir($appDir);
174
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
175
176
        // composer update
177
        $this->stdout("updating composer for app '$app'...\n", Console::BOLD);
178
        chdir($appDir);
179
        $command = 'composer update --prefer-dist';
180
        if ($this->composerNoProgress) {
181
            $command .= ' --no-progress';
182
        }
183
        passthru($command);
184
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
185
186
        // link directories
187
        $this->stdout("linking framework and extensions to '$app' app vendor dir...\n", Console::BOLD);
188
        $this->linkFrameworkAndExtensions($appDir, $base);
189
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
190
191
        return 0;
192
    }
193
194
    /**
195
     * This command installs an extension in the `extensions` directory and links the framework and other extensions.
196
     *
197
     * @param string $extension the application name e.g. `basic` or `advanced`.
198
     * @param string $repo url of the git repo to clone if it does not already exist.
199
     *
200
     * @return int
201
     */
202
    public function actionExt($extension, $repo = null)
203
    {
204
        // root of the dev repo
205
        $base = \dirname(\dirname(__DIR__));
206
        $extensionDir = "$base/extensions/$extension";
207
208
        if (!file_exists($extensionDir)) {
209
            if (empty($repo)) {
210
                if (isset($this->extensions[$extension])) {
211
                    $repo = $this->extensions[$extension];
212
                    if ($this->useHttp) {
213
                        $repo = str_replace('[email protected]:', 'https://github.com/', $repo);
214
                    }
215
                } else {
216
                    $this->stderr("Repo argument is required for extension '$extension'.\n", Console::FG_RED);
217
                    return 1;
218
                }
219
            }
220
221
            $this->stdout("cloning extension repo '$extension' from '$repo'...\n", Console::BOLD);
222
            passthru('git clone ' . escapeshellarg($repo) . ' ' . $extensionDir);
223
            $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
224
        }
225
226
        // cleanup
227
        $this->stdout("cleaning up extension '$extension' vendor directory...\n", Console::BOLD);
228
        $this->cleanupVendorDir($extensionDir);
229
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
230
231
        // composer update
232
        $this->stdout("updating composer for extension '$extension'...\n", Console::BOLD);
233
        chdir($extensionDir);
234
        $command = 'composer update --prefer-dist';
235
        if ($this->composerNoProgress) {
236
            $command .= ' --no-progress';
237
        }
238
        passthru($command);
239
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
240
241
        // link directories
242
        $this->stdout("linking framework and extensions to '$extension' vendor dir...\n", Console::BOLD);
243
        $this->linkFrameworkAndExtensions($extensionDir, $base);
244
        $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
245
246
        return 0;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function options($actionID)
253
    {
254
        $options = parent::options($actionID);
255
        if (\in_array($actionID, ['ext', 'app', 'all'], true)) {
256
            $options[] = 'useHttp';
257
            $options[] = 'composerNoProgress';
258
        }
259
260
        return $options;
261
    }
262
263
264
    /**
265
     * Remove all symlinks in the vendor subdirectory of the directory specified.
266
     * @param string $dir base directory
267
     */
268
    protected function cleanupVendorDir($dir)
269
    {
270
        if (is_link($link = "$dir/vendor/yiisoft/yii2")) {
271
            $this->stdout("Removing symlink $link.\n");
272
            FileHelper::unlink($link);
273
        }
274
        $extensions = $this->findDirs("$dir/vendor/yiisoft");
275
        foreach ($extensions as $ext) {
276
            if (is_link($link = "$dir/vendor/yiisoft/yii2-$ext")) {
277
                $this->stdout("Removing symlink $link.\n");
278
                FileHelper::unlink($link);
279
            }
280
        }
281
    }
282
283
    /**
284
     * Creates symlinks to framework and extension sources for the application.
285
     * @param string $dir application directory
286
     * @param string $base Yii sources base directory
287
     *
288
     * @return int
289
     */
290
    protected function linkFrameworkAndExtensions($dir, $base)
291
    {
292
        if (is_dir($link = "$dir/vendor/yiisoft/yii2")) {
293
            $this->stdout("Removing dir $link.\n");
294
            FileHelper::removeDirectory($link);
295
            $this->stdout("Creating symlink for $link.\n");
296
            symlink("$base/framework", $link);
297
        }
298
        $extensions = $this->findDirs("$dir/vendor/yiisoft");
299
        foreach ($extensions as $ext) {
300
            if (is_dir($link = "$dir/vendor/yiisoft/yii2-$ext")) {
301
                $this->stdout("Removing dir $link.\n");
302
                FileHelper::removeDirectory($link);
303
                $this->stdout("Creating symlink for $link.\n");
304
                if (!file_exists("$base/extensions/$ext")) {
305
                    $ret = $this->actionExt($ext);
306
                    if ($ret !== 0) {
307
                        return $ret;
308
                    }
309
                }
310
                symlink("$base/extensions/$ext", $link);
311
            }
312
        }
313
    }
314
315
    /**
316
     * Get a list of subdirectories for directory specified.
317
     * @param string $dir directory to read
318
     *
319
     * @return array list of subdirectories
320
     */
321
    protected function listSubDirs($dir)
322
    {
323
        $list = [];
324
        $handle = opendir($dir);
325
        if ($handle === false) {
326
            throw new InvalidParamException("Unable to open directory: $dir");
327
        }
328
        while (($file = readdir($handle)) !== false) {
329
            if ($file === '.' || $file === '..') {
330
                continue;
331
            }
332
            // ignore hidden directories
333
            if ($file[0] === '.') {
334
                continue;
335
            }
336
            if (is_dir("$dir/$file")) {
337
                $list[] = "$dir/$file";
338
            }
339
        }
340
        closedir($handle);
341
        return $list;
342
    }
343
344
    /**
345
     * Finds linkable applications.
346
     *
347
     * @param string $dir directory to search in
348
     * @return array list of applications command can link
349
     */
350
    protected function findDirs($dir)
351
    {
352
        $list = [];
353
        $handle = @opendir($dir);
354
        if ($handle === false) {
355
            return [];
356
        }
357
        while (($file = readdir($handle)) !== false) {
358
            if ($file === '.' || $file === '..') {
359
                continue;
360
            }
361
            $path = $dir . DIRECTORY_SEPARATOR . $file;
362
            if (is_dir($path) && preg_match('/^yii2-(.*)$/', $file, $matches)) {
363
                $list[] = $matches[1];
364
            }
365
        }
366
        closedir($handle);
367
368
        foreach ($list as $i => $e) {
369
            if ($e === 'composer') { // skip composer to not break composer update
370
                unset($list[$i]);
371
            }
372
        }
373
374
        return $list;
375
    }
376
}
377