Completed
Push — master ( fad484...3eeff7 )
by Song
02:31
created

ExtendCommand::copy()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 2
dl 0
loc 18
rs 9.3554
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Console;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Filesystem\Filesystem;
7
8
class ExtendCommand extends Command
9
{
10
    /**
11
     * The console command name.
12
     *
13
     * @var string
14
     */
15
    protected $signature = 'admin:extend {extension} {--namespace=}';
16
17
    /**
18
     * The console command description.
19
     *
20
     * @var string
21
     */
22
    protected $description = 'Build a Laravel-admin extension';
23
24
    /**
25
     * @var string
26
     */
27
    protected $basePath = '';
28
29
    /**
30
     * @var Filesystem
31
     */
32
    protected $filesystem;
33
34
    /**
35
     * @var string
36
     */
37
    protected $namespace;
38
39
    /**
40
     * @var string
41
     */
42
    protected $className;
43
44
    /**
45
     * @var string
46
     */
47
    protected $package;
48
49
    /**
50
     * @var string
51
     */
52
    protected $extensionDir;
53
54
    /**
55
     * @var array
56
     */
57
    protected $dirs = [
58
        'database/migrations',
59
        'database/seeds',
60
        'resources/assets',
61
        'resources/views',
62
        'src/Http/Controllers',
63
        'routes',
64
    ];
65
66
    /**
67
     * Execute the console command.
68
     *
69
     * @return void
70
     */
71
    public function handle(Filesystem $filesystem)
72
    {
73
        $this->filesystem = $filesystem;
74
75
        $this->extensionDir = config('admin.extension_dir');
76
77
        InputExtensionDir:
78
        if (empty($this->extensionDir)) {
79
            $this->extensionDir = $this->ask('Please input a directory to store your extension:');
80
        }
81
82
        if (!file_exists($this->extensionDir)) {
83
            goto InputExtensionDir;
84
        }
85
86
        $this->package = $this->argument('extension');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->argument('extension') can also be of type array. However, the property $package is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
87
88
        InputExtensionName:
89
        if (!$this->validateExtensionName($this->package)) {
0 ignored issues
show
Bug introduced by
It seems like $this->package can also be of type array or null; however, Encore\Admin\Console\Ext...validateExtensionName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
90
            $this->package = $this->ask("[$this->package] is not a valid package name, please input a name like (<vendor>/<name>)");
91
            goto InputExtensionName;
92
        }
93
94
        $this->makeDirs();
95
        $this->makeFiles();
96
97
        $this->info("The extension scaffolding generated successfully. \r\n");
98
        $this->showTree();
99
    }
100
101
    /**
102
     * Show extension scaffolding with tree structure.
103
     */
104
    protected function showTree()
105
    {
106
        $tree = <<<TREE
107
{$this->extensionPath()}
108
    ├── LICENSE
109
    ├── README.md
110
    ├── composer.json
111
    ├── database
112
    │   ├── migrations
113
    │   └── seeds
114
    ├── resources
115
    │   ├── assets
116
    │   └── views
117
    │       └── index.blade.php
118
    ├── routes
119
    │   └── web.php
120
    └── src
121
        ├── {$this->className}.php
122
        ├── {$this->className}ServiceProvider.php
123
        └── Http
124
            └── Controllers
125
                └── {$this->className}Controller.php
126
TREE;
127
128
        $this->info($tree);
129
    }
130
131
    /**
132
     * Make extension files.
133
     */
134
    protected function makeFiles()
135
    {
136
        $this->namespace = $this->getRootNameSpace();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getRootNameSpace() can also be of type array. However, the property $namespace is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
137
138
        $this->className = $this->getClassName();
139
140
        // copy files
141
        $this->copy([
142
            __DIR__.'/stubs/extension/view.stub'       => 'resources/views/index.blade.php',
143
            __DIR__.'/stubs/extension/.gitignore.stub' => '.gitignore',
144
            __DIR__.'/stubs/extension/README.md.stub'  => 'README.md',
145
            __DIR__.'/stubs/extension/LICENSE.stub'    => 'LICENSE',
146
        ]);
147
148
        // make composer.json
149
        $composerContents = str_replace(
150
            [':package', ':namespace', ':class_name'],
151
            [$this->package, str_replace('\\', '\\\\', $this->namespace).'\\\\', $this->className],
152
            file_get_contents(__DIR__.'/stubs/extension/composer.json.stub')
153
        );
154
        $this->putFile('composer.json', $composerContents);
155
156
        // make class
157
        $classContents = str_replace(
158
            [':namespace', ':class_name', ':title', ':path', ':base_package'],
159
            [$this->namespace, $this->className, title_case($this->className), basename($this->package), basename($this->package)],
160
            file_get_contents(__DIR__.'/stubs/extension/extension.stub')
161
        );
162
        $this->putFile("src/{$this->className}.php", $classContents);
163
164
        // make service provider
165
        $providerContents = str_replace(
166
            [':namespace', ':class_name', ':base_package', ':package'],
167
            [$this->namespace, $this->className, basename($this->package), $this->package],
168
            file_get_contents(__DIR__.'/stubs/extension/service-provider.stub')
169
        );
170
        $this->putFile("src/{$this->className}ServiceProvider.php", $providerContents);
171
172
        // make controller
173
        $controllerContent = str_replace(
174
            [':namespace', ':class_name', ':base_package'],
175
            [$this->namespace, $this->className, basename($this->package)],
176
            file_get_contents(__DIR__.'/stubs/extension/controller.stub')
177
        );
178
        $this->putFile("src/Http/Controllers/{$this->className}Controller.php", $controllerContent);
179
180
        // make routes
181
        $routesContent = str_replace(
182
            [':namespace', ':class_name', ':path'],
183
            [$this->namespace, $this->className, basename($this->package)],
184
            file_get_contents(__DIR__.'/stubs/extension/routes.stub')
185
        );
186
        $this->putFile('routes/web.php', $routesContent);
187
    }
188
189
    /**
190
     * Get root namespace for this package.
191
     *
192
     * @return array|null|string
193
     */
194
    protected function getRootNameSpace()
195
    {
196
        if (!$namespace = $this->option('namespace')) {
197
            list($vendor, $name) = explode('/', $this->package);
198
199
            $default = str_replace(['-', '-'], '', title_case($vendor).'\\'.title_case($name));
200
201
            $namespace = $this->ask('Root namespace', $default);
202
        }
203
204
        return $namespace;
205
    }
206
207
    /**
208
     * Get extension class name.
209
     *
210
     * @return string
211
     */
212
    protected function getClassName()
213
    {
214
        return class_basename($this->namespace);
215
    }
216
217
    /**
218
     * Create package dirs.
219
     */
220
    protected function makeDirs()
221
    {
222
        $this->basePath = rtrim($this->extensionDir, '/').'/'.ltrim($this->package, '/');
223
224
        $this->makeDir($this->dirs);
225
    }
226
227
    /**
228
     * Validate extension name.
229
     *
230
     * @param string $name
231
     *
232
     * @return int
233
     */
234
    protected function validateExtensionName($name)
235
    {
236
        return preg_match('/^[\w-_]+\/[\w-_]+$/', $name);
237
    }
238
239
    /**
240
     * Extension path.
241
     *
242
     * @param string $path
243
     *
244
     * @return string
245
     */
246
    protected function extensionPath($path = '')
247
    {
248
        $path = rtrim($path, '/');
249
250
        if (empty($path)) {
251
            return rtrim($this->basePath, '/');
252
        }
253
254
        return rtrim($this->basePath, '/').'/'.ltrim($path, '/');
255
    }
256
257
    /**
258
     * Put contents to file.
259
     *
260
     * @param string $to
261
     * @param string $content
262
     */
263
    protected function putFile($to, $content)
264
    {
265
        $to = $this->extensionPath($to);
266
267
        $this->filesystem->put($to, $content);
268
    }
269
270
    /**
271
     * Copy files to extension path.
272
     *
273
     * @param string|array $from
274
     * @param string|null  $to
275
     */
276
    protected function copy($from, $to = null)
277
    {
278
        if (is_array($from) && is_null($to)) {
279
            foreach ($from as $key => $value) {
280
                $this->copy($key, $value);
281
            }
282
283
            return;
284
        }
285
286
        if (!file_exists($from)) {
287
            return;
288
        }
289
290
        $to = $this->extensionPath($to);
291
292
        $this->filesystem->copy($from, $to);
0 ignored issues
show
Bug introduced by
It seems like $from defined by parameter $from on line 276 can also be of type array; however, Illuminate\Filesystem\Filesystem::copy() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
293
    }
294
295
    /**
296
     * Make new directory.
297
     *
298
     * @param array|string $paths
299
     */
300
    protected function makeDir($paths)
301
    {
302
        foreach ((array) $paths as $path) {
303
            $path = $this->extensionPath($path);
304
305
            $this->filesystem->makeDirectory($path, 0755, true, true);
306
        }
307
    }
308
}
309