Completed
Push — master ( 906de7...68fd99 )
by Martijn van
02:08
created

MakeCommand::mergeCustomDirectoriesWithDefault()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 3
eloc 5
nc 3
nop 2
1
<?php
2
3
4
use Symfony\Component\Console\Input\InputArgument;
5
use Symfony\Component\Console\Input\InputOption;
6
7
abstract class MakeCommand extends SilverstripeCommand
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...
8
{
9
    /**
10
     * @config
11
     * @var array
12
     */
13
    private static $default_dirs = [
14
        'Command'             => 'mysite/code/commands',
15
        'Controller'          => 'mysite/code/controllers',
16
        'ControllerExtension' => 'mysite/code/extensions',
17
        'DataObject'          => 'mysite/code/dataobjects',
18
        'DataExtension'       => 'mysite/code/extensions',
19
        'Extension'           => 'mysite/code/extensions',
20
        'Form'                => 'mysite/code/forms',
21
        'Page'                => 'mysite/code/pages',
22
        'Test'                => 'mysite/tests',
23
        'Theme'               => 'themes'
24
    ];
25
26
    public function fire()
27
    {
28
        $class  = $this->getNameInput();
29
30
        if(!(bool)$class) {
31
            $this->error('Empty name given. Are you running a test?');
32
            return false;
33
        }
34
35
        $target = $this->getTargetFile($class);
36
37
        if($this->classOrFileExists($target, $class)) {
0 ignored issues
show
Bug introduced by
It seems like $class defined by $this->getNameInput() on line 28 can also be of type array; however, MakeCommand::classOrFileExists() 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...
38
            return false;
39
        }
40
41
        $this->writeFile($target, $class);
0 ignored issues
show
Bug introduced by
It seems like $class defined by $this->getNameInput() on line 28 can also be of type array; however, MakeCommand::writeFile() 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...
42
43
        $this->clearCache();
44
    }
45
46
    /**
47
     * @param string $target
48
     * @param string $class
49
     * @return bool
50
     */
51
    protected function classOrFileExists($target, $class)
52
    {
53
        if ($this->classExists($class)) {
54
            $this->error('class ' . $class . ' already exists!');
55
            return true;
56
        }elseif ($this->fileExists($class)) {
57
            $this->error('file ' . str_replace(BASE_PATH, '', $target). ' already exists!');
58
            return true;
59
        }
60
        return false;
61
    }
62
63
    /**
64
     * @param string $target
65
     * @param string $class
66
     */
67
    protected function writeFile($target, $class)
68
    {
69
        $this->makeDirectory();
70
71
        file_put_contents($target, $this->buildClass($class));
72
73
        $this->info($class . ' created successfully in ' . str_replace(BASE_PATH, '', $target));
74
    }
75
76
    protected function clearCache()
77
    {
78
        if ($this->option('clearcache')) {
79
            $this->call('cache:clear');
80
        } else {
81
            $this->warn('to use the class, please run cache:clear');
82
        }
83
    }
84
85
    /**
86
     * @return string
87
     */
88
    public function getPhpStub()
89
    {
90
        return $this->getStubFilePath($this->getCommandClass());
91
    }
92
93
    /**
94
     * @return string
95
     */
96
    public function getTemplateStub()
97
    {
98
        return Str::replaceLast('.php.stub', '.ss.stub', $this->getPhpStub());
99
    }
100
101
    /**
102
     * @return string
103
     */
104
    public function getTargetDirectory()
105
    {
106
        $class  = $this->getCommandClass();
107
        $dirs   = (array)self::$default_dirs;
108
        $custom = $this->getTargetDirectoryByOptionOrConfig();
109
110
        // --dir=mymodule || MakeCommand.default_dirs = mymodule || MakeCommand.default_dirs = mymodule/somedir
111
        if (is_string($custom)) {
112
            $dirs = $this->setTargetDirectoriesByString($custom, $dirs);
113
            if(is_string($dirs)) {
114
                return $dirs;
115
            }
116
        // MakeCommand.default_dirs = array()
117
        } elseif (is_array($custom)) {
118
            $dirs = $this->mergeCustomDirectoriesWithDefault($custom, $dirs) ;
119
        }
120
121
        return isset($dirs[$class]) ? BASE_PATH . '/' . $dirs[$class] : '';
122
    }
123
124
    /**
125
     * @param string $customDir
126
     * @param array $defaultDirs
127
     * @return string|array
128
     */
129
    protected function setTargetDirectoriesByString($customDir, $defaultDirs)
130
    {
131
        // MakeCommand.default_dirs = mymodule/somedir
132
        if(Str::contains($customDir, '/')) {
133
            return BASE_PATH . '/' . $customDir;
134
        }
135
136
        // MakeCommand.default_dirs = mymodule
137
        foreach ($defaultDirs as $key => $dir) {
138
            $defaultDirs[$key] = Str::replaceFirst('mysite', $customDir, $dir);
139
        }
140
141
        return $defaultDirs;
142
    }
143
144
    /**
145
     * @param array $customDirs
146
     * @param array $defaultDirs
147
     * @return array
148
     */
149
    protected function mergeCustomDirectoriesWithDefault($customDirs, $defaultDirs)
150
    {
151
        foreach($customDirs as $key => $dir) {
152
            if(is_string($key)) {
153
                $defaultDirs[$key] = $dir;
154
            }
155
        }
156
        return $defaultDirs;
157
    }
158
159
    /**
160
     * The absolute file path
161
     *
162
     * @return string
163
     */
164
    public function getTargetFile($class)
165
    {
166
        return $this->getTargetDirectory() . '/' . $class . '.php';
167
    }
168
169
    /**
170
     * Gets the Classname to generate from the called class like
171
     * MakeDataObjectCommand => DataObject class
172
     * MakeFormCommand       => Form class
173
     *
174
     * @return string
175
     */
176
    public function getCommandClass()
177
    {
178
        $class = get_class($this);
179
        $class = Str::replaceFirst('Make', '', $class);
180
        $class = Str::replaceLast('Command', '', $class);
181
182
        return $class;
183
    }
184
185
    /**
186
     * @throws InvalidArgumentException
187
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array|integer|double|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
188
     */
189
    protected function getTargetDirectoryByOptionOrConfig()
190
    {
191
        if($this->option('dir')) {
192
            return $this->option('dir');
193
        }
194
195
        return Config::inst()->get('MakeCommand', 'default_dirs');
196
    }
197
198
    /**
199
     * @return string
200
     */
201
    protected function getStubFilePath($commandClass)
202
    {
203
        $customStubFilePath = $this->getCustomStubPath($commandClass);
204
        if (is_file($customStubFilePath)) {
205
            return $customStubFilePath;
206
        }
207
208
        $mysiteStubFilePath = $this->getCustomStubPath($commandClass);
209
210
        if (is_file($mysiteStubFilePath)) {
211
            return $mysiteStubFilePath;
212
        }
213
214
        return $this->getConsoleStubPath($commandClass);
215
    }
216
217
    /**
218
     * @return string
219
     */
220
    protected function getConsoleStubPath($commandClass)
221
    {
222
        return BASE_PATH . '/console/stubs/' . $commandClass . '.php.stub';
223
    }
224
225
    /**
226
     * @return string
227
     */
228
    protected function getMySiteStubPath($commandClass)
229
    {
230
        return BASE_PATH . '/mysite/stubs/' . $commandClass . '.php.stub';
231
    }
232
233
    /**
234
     * @return string
235
     */
236
    protected function getCustomStubPath($commandClass)
237
    {
238
        $stubDir = Config::inst()->get('MakeCommand', 'stub_dir');
239
240
        if ($stubDir) {
241
            return BASE_PATH . '/' . $stubDir . '/' . $commandClass . '.php.stub';
242
        }
243
        return '';
244
    }
245
246
    /**
247
     * @param $class
248
     * @return bool
249
     */
250
    protected function fileExists($class)
251
    {
252
        return is_file($this->getTargetFile($class));
253
    }
254
255
    /**
256
     * @param $class
257
     * @return bool
258
     */
259
    protected function classExists($class)
260
    {
261
        return ClassInfo::exists($class);
262
    }
263
264
    /**
265
     * Build the directory for the class if necessary.
266
     *
267
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
268
     */
269
    protected function makeDirectory()
270
    {
271
        $path = $this->getTargetDirectory();
272
273
        if (!is_dir($path)) {
274
            mkdir($path, 0777, true);
275
276
            $this->info('directory ' . str_replace(BASE_PATH, '', $path) . ' created');
277
        }
278
    }
279
280
    /**
281
     * Replace the class name for the given stub.
282
     *
283
     * @param  string  $class
284
     * @return string
285
     */
286
    protected function buildClass($class)
287
    {
288
        return str_replace('DummyClass', $class, file_get_contents($this->getPhpStub()));
289
    }
290
291
    /**
292
     * Get the desired class name from the input.
293
     *
294
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
295
     */
296
    protected function getNameInput()
297
    {
298
        return $this->argument('name');
299
    }
300
301
    protected function getOptions()
302
    {
303
        return [
304
            ['clearcache', 'c', InputOption::VALUE_NONE, 'Clear the cache after adding the class'],
305
            ['dir', 'd', InputOption::VALUE_OPTIONAL, 'Set the directory to write the file to']
306
        ];
307
    }
308
309
    /**
310
     * Get the console command arguments.
311
     *
312
     * @return array
313
     */
314
    protected function getArguments()
315
    {
316
        return [
317
            ['name', InputArgument::REQUIRED, 'The name of the class'],
318
        ];
319
    }
320
}
321