Completed
Push — master ( 1dd7d1...0c5b7b )
by Tom
09:54 queued 05:02
created

ScriptCommand::initScriptVars()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 3 Features 0
Metric Value
c 5
b 3
f 0
dl 0
loc 14
rs 9.4285
cc 2
eloc 10
nc 2
nop 0
1
<?php
2
3
namespace N98\Magento\Command;
4
5
use N98\Util\BinaryString;
6
use RuntimeException;
7
use Symfony\Component\Console\Helper\DialogHelper;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Input\StringInput;
12
use Symfony\Component\Console\Output\OutputInterface;
13
14
class ScriptCommand extends AbstractMagentoCommand
15
{
16
    /**
17
     * @var array
18
     */
19
    protected $scriptVars = array();
20
21
    /**
22
     * @var string
23
     */
24
    protected $_scriptFilename = '';
25
26
    /**
27
     * @var bool
28
     */
29
    protected $_stopOnError = false;
30
31
    /**
32
     * @var null|\Magento\Framework\App\ProductMetadata
33
     */
34
    protected $productMetadata = null;
35
36
37 View Code Duplication
    protected function configure()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
38
    {
39
        $this
40
            ->setName('script')
41
            ->addArgument('filename', InputArgument::OPTIONAL, 'Script file')
42
            ->addOption('define', 'd', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Defines a variable')
43
            ->addOption('stop-on-error', null, InputOption::VALUE_NONE, 'Stops execution of script on error')
44
            ->setDescription('Runs multiple n98-magerun commands');
45
46
        $help = <<<HELP
47
Example:
48
49
   # Set multiple config
50
   config:set "web/cookie/cookie_domain" example.com
51
52
   # Set with multiline values with "\n"
53
   config:set "general/store_information/address" "First line\nSecond line\nThird line"
54
55
   # This is a comment
56
   cache:flush
57
58
59
Optionally you can work with unix pipes.
60
61
   \$ echo "cache:flush" | n98-magerun-dev script
62
63
   \$ n98-magerun.phar script < filename
64
65
It is even possible to create executable scripts:
66
67
Create file `test.magerun` and make it executable (`chmod +x test.magerun`):
68
69
   #!/usr/bin/env n98-magerun.phar script
70
71
   config:set "web/cookie/cookie_domain" example.com
72
   cache:flush
73
74
   # Run a shell script with "!" as first char
75
   ! ls -l
76
77
   # Register your own variable (only key = value currently supported)
78
   \${my.var}=bar
79
80
   # Let magerun ask for variable value - add a question mark
81
   \${my.var}=?
82
83
   ! echo \${my.var}
84
85
   # Use resolved variables from n98-magerun in shell commands
86
   ! ls -l \${magento.root}/code/local
87
88
Pre-defined variables:
89
90
* \${magento.root}    -> Magento Root-Folder
91
* \${magento.version} -> Magento Version i.e. 1.7.0.2
92
* \${magento.edition} -> Magento Edition -> Community or Enterprise
93
* \${magerun.version} -> Magerun version i.e. 1.66.0
94
* \${php.version}     -> PHP Version
95
* \${script.file}     -> Current script file path
96
* \${script.dir}      -> Current script file dir
97
98
Variables can be passed to a script with "--define (-d)" option.
99
100
Example:
101
102
   $ n98-magerun.phar script -d foo=bar filename
103
104
   # This will register the variable \${foo} with value bar.
105
106
It's possible to define multiple values by passing more than one option.
107
HELP;
108
        $this->setHelp($help);
109
    }
110
111
    /**
112
     * @return bool
113
     */
114
    public function isEnabled()
115
    {
116
        return function_exists('exec');
117
    }
118
119
    protected function execute(InputInterface $input, OutputInterface $output)
120
    {
121
        $this->_scriptFilename = $input->getArgument('filename');
122
        $this->_stopOnError = $input->getOption('stop-on-error');
123
        $this->_initDefines($input);
124
        $script = $this->_getContent($this->_scriptFilename);
125
        $commands = explode("\n", $script);
126
        $this->initScriptVars();
127
128
        foreach ($commands as $commandString) {
129
            $commandString = trim($commandString);
130
            if (empty($commandString)) {
131
                continue;
132
            }
133
            $firstChar = substr($commandString, 0, 1);
134
135
            switch ($firstChar) {
136
137
                // comment
138
                case '#':
139
                    continue;
140
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
141
142
                // set var
143
                case '$':
144
                    $this->registerVariable($output, $commandString);
145
                    break;
146
147
                // run shell script
148
                case '!':
149
                    $this->runShellCommand($output, $commandString);
150
                    break;
151
152
                default:
153
                    $this->runMagerunCommand($input, $output, $commandString);
154
            }
155
        }
156
    }
157
158
    /**
159
     * @param InputInterface $input
160
     * @throws \InvalidArgumentException
161
     */
162
    protected function _initDefines(InputInterface $input)
163
    {
164
        $defines = (array) $input->getOption('define');
165
166
        foreach ($defines as $define) {
167
            if (!strstr($define, '=')) {
168
                throw new \InvalidArgumentException('Invalid define');
169
            }
170
            $parts = BinaryString::trimExplodeEmpty('=', $define);
171
            list($variable, $value) = $parts + [1 => null];
172
            $this->scriptVars['${' . $variable . '}'] = $value;
173
        }
174
    }
175
176
    /**
177
     * @param string $filename
178
     * @throws RuntimeException
179
     * @internal param string $input
180
     * @return string
181
     */
182
    protected function _getContent($filename)
183
    {
184
        if ($filename === '-' || empty($filename)) {
185
            $filename = 'php://stdin';
186
        }
187
        $script = @\file_get_contents($filename);
188
189
        if (!$script) {
190
            throw new RuntimeException('Script file was not found');
191
        }
192
193
        return $script;
194
    }
195
196
    /**
197
     * @param OutputInterface $output
198
     * @param string $commandString
199
     * @throws RuntimeException
200
     * @return void
201
     */
202
    protected function registerVariable(OutputInterface $output, $commandString)
203
    {
204
        if (preg_match('/^(\$\{[a-zA-Z0-9-_.]+\})=(.+)/', $commandString, $matches)) {
205
            if (isset($matches[2]) && $matches[2][0] == '?') {
206
207
                // Variable is already defined
208
                if (isset($this->scriptVars[$matches[1]])) {
209
                    return $this->scriptVars[$matches[1]];
210
                }
211
212
                /* @var $dialog DialogHelper */
213
                $dialog = $this->getHelper('dialog');
214
215
                /**
216
                 * Check for select "?["
217
                 */
218
                if (isset($matches[2][1]) && $matches[2][1] == '[') {
219
                    if (preg_match('/\[(.+)\]/', $matches[2], $choiceMatches)) {
220
                        $choices = BinaryString::trimExplodeEmpty(',', $choiceMatches[1]);
221
                        $selectedIndex = $dialog->select(
222
                            $output,
223
                            '<info>Please enter a value for <comment>' . $matches[1] . '</comment>:</info> ',
224
                            $choices
225
                        );
226
                        $this->scriptVars[$matches[1]] = $choices[$selectedIndex];
227
                    } else {
228
                        throw new RuntimeException('Invalid choices');
229
                    }
230
                } else {
231
                    // normal input
232
                    $this->scriptVars[$matches[1]] = $dialog->askAndValidate(
233
                        $output,
234
                        '<info>Please enter a value for <comment>' . $matches[1] . '</comment>:</info> ',
235
                        function ($value) {
236
                            if ($value == '') {
237
                                throw new \Exception('Please enter a value');
238
                            }
239
240
                            return $value;
241
                        }
242
                    );
243
                }
244
            } else {
245
                $this->scriptVars[$matches[1]] = $this->_replaceScriptVars($matches[2]);
246
            }
247
        }
248
    }
249
250
    /**
251
     * @param InputInterface $input
252
     * @param OutputInterface $output
253
     * @param string $commandString
254
     * @throws RuntimeException
255
     */
256
    protected function runMagerunCommand(InputInterface $input, OutputInterface $output, $commandString)
0 ignored issues
show
Unused Code introduced by
The parameter $input 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...
257
    {
258
        $this->getApplication()->setAutoExit(false);
259
        $commandString = $this->_replaceScriptVars($commandString);
260
        $input = new StringInput($commandString);
261
        $exitCode = $this->getApplication()->run($input, $output);
262
        if ($exitCode !== 0 && $this->_stopOnError) {
263
            throw new RuntimeException('Script stopped with errors');
264
        }
265
    }
266
267
    /**
268
     * @param string $commandString
269
     * @return string
270
     */
271
    protected function _prepareShellCommand($commandString)
272
    {
273
        $commandString = ltrim($commandString, '!');
274
275
        // @TODO find a better place
276
        if (strstr($commandString, '${magento.root}')
277
            || strstr($commandString, '${magento.version}')
278
            || strstr($commandString, '${magento.edition}')
279
        ) {
280
            $this->initMagento();
281
        }
282
        $this->initScriptVars();
283
        $commandString = $this->_replaceScriptVars($commandString);
284
285
        return $commandString;
286
    }
287
288
    protected function initScriptVars()
289
    {
290
        $rootFolder = $this->getApplication()->getMagentoRootFolder();
291
        if (!empty($rootFolder)) {
292
            $this->scriptVars['${magento.root}'] = $rootFolder;
293
            $this->scriptVars['${magento.version}'] = $this->getMagentoVersion();
294
            $this->scriptVars['${magento.edition}'] = $this->getMagentoEdition();
295
        }
296
297
        $this->scriptVars['${php.version}'] = substr(phpversion(), 0, strpos(phpversion(), '-'));
298
        $this->scriptVars['${magerun.version}'] = $this->getApplication()->getVersion();
299
        $this->scriptVars['${script.file}'] = $this->_scriptFilename;
300
        $this->scriptVars['${script.dir}'] = dirname($this->_scriptFilename);
301
    }
302
303
    /**
304
     * @param OutputInterface $output
305
     * @param string $commandString
306
     * @internal param $returnValue
307
     */
308
    protected function runShellCommand(OutputInterface $output, $commandString)
309
    {
310
        $commandString = $this->_prepareShellCommand($commandString);
311
        $returnValue = shell_exec($commandString);
312
        if (!empty($returnValue)) {
313
            $output->writeln($returnValue);
314
        }
315
    }
316
317
    /**
318
     * @param string $commandString
319
     * @return string
320
     */
321
    protected function _replaceScriptVars($commandString)
322
    {
323
        $commandString = str_replace(array_keys($this->scriptVars), $this->scriptVars, $commandString);
324
325
        return $commandString;
326
    }
327
328
    /**
329
     * @return string
330
     */
331
    private function getMagentoVersion()
332
    {
333
        return $this->getProductMetadata()->getVersion();
334
    }
335
336
    /**
337
     *
338
     * @return mixed
339
     */
340
    private function getMagentoEdition()
341
    {
342
        return $this->getProductMetadata()->getEdition();
343
    }
344
345
    /**
346
     * @return \Magento\Framework\App\ProductMetadata
347
     */
348
    private function getProductMetadata()
349
    {
350
        if (is_null($this->productMetadata)) {
351
            $this->initMagento(); // obtaining the object-manager requires init Magento
352
            $objectManager = $this->getApplication()->getObjectManager();
353
            $this->productMetadata = $objectManager->get('\Magento\Framework\App\ProductMetadata');
354
        }
355
356
        return $this->productMetadata;
357
    }
358
}
359