├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json └── src ├── ActionGroup.php ├── Bridge.php ├── ConsoleOutput.php ├── ErrorHandler.php ├── OutputStyle.php └── base ├── Action.php ├── ArtisanOutputTrait.php ├── BlockOutputTrait.php └── Commands.php /.gitignore: -------------------------------------------------------------------------------- 1 | # COMPOSER 2 | /vendor 3 | composer.lock 4 | 5 | 6 | # MISC FILES 7 | .cache 8 | .DS_Store 9 | .idea 10 | .project 11 | .settings 12 | *.esproj 13 | *.sublime-workspace 14 | *.sublime-project 15 | *.tmproj 16 | *.tmproject 17 | .vscode/* 18 | !.vscode/settings.json 19 | !.vscode/tasks.json 20 | !.vscode/launch.json 21 | !.vscode/extensions.json 22 | config.codekit3 23 | prepros-6.config 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Oliver Stark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii2 Artisan Bridge 2 | 3 | This library brings the ease of Artisan commands and the power of Symfony console to Yii2 and Craft 5. 4 | 5 | ## Install 6 | 7 | Require the package: 8 | ``` 9 | composer require ostark/yii2-artisan-bridge 10 | ``` 11 | 12 | ### Configure actions for a Craft 5 plugin 13 | ```php 14 | setActions([ 39 | 'action1' => ActionOne::class, 40 | 'action2' => ActionTwo::class, 41 | ]) 42 | ->setDefaultAction('action1') 43 | ->setOptions([ 44 | 'one' => 'option-one', 45 | 'two' => 'option-two', 46 | 'option-without-alias' 47 | ]); 48 | 49 | Bridge::registerGroup($group); 50 | } 51 | } 52 | } 53 | 54 | ``` 55 | 56 | ### Write your Actions (Commands) 57 | 58 | You write one class per action. Your actual instructions live in the `run()` method, similar to 59 | `execute()` in Symfony or `handle()` in Laravel. Command arguments map to the arguments of the `run()` method. 60 | 61 | Options and option aliases are registered in Commands::register($prefix, $actions, `$options`). To access an option, 62 | it must be declared as a public property in the Action class. 63 | 64 | ```php 65 | title("Hello {$name}, 'option-one' is '{$this->optionOne}'"); 84 | 85 | $answer = $this->choice("What's your favorite animal?", ['Dog','Cat','Elephant']); 86 | 87 | if ($answer === 'Elephant') { 88 | $this->successBlock("'$answer' is correct."); 89 | return ExitCode::OK; 90 | } else { 91 | $this->errorBlock("'$answer' is the wrong."); 92 | return ExitCode::UNSPECIFIED_ERROR; 93 | } 94 | 95 | } 96 | } 97 | ``` 98 | 99 | ### Artisan helper methods 100 | 101 | **Prompting for input** 102 | 103 | ```php 104 | $name = $this->ask('What is your name?', $default = null)` 105 | ``` 106 | 107 | ```php 108 | $name = $this->anticipate('What is your name?', ['Taylor', 'Fabien', 'Brad', 'Brandon']); 109 | ``` 110 | 111 | ```php 112 | if ($this->confirm('Do you wish to continue?')) { 113 | // continue 114 | } 115 | ``` 116 | 117 | 118 | **Writing output** 119 | 120 | ```php 121 | $this->info('Display this on the screen'); 122 | $this->error('Something went wrong!'); 123 | ``` 124 | 125 | ```php 126 | $headers = ['Name', 'Email']; 127 | $rows = [['First name', 'First email'], ['Second name', 'Second email']]; 128 | 129 | $this->table($headers, $rows); 130 | ``` 131 | 132 | 133 | 134 | ### Symfony block style 135 | 136 | ```php 137 | $this->title('Title style block'); 138 | $this->section('Section style block'); 139 | $this->listing(['One','Two','Three']; 140 | 141 | $this->successBlock('Yeah!'); 142 | $this->errorBlock('Oh no!'); 143 | 144 | // Custom blocks 145 | $this->block($messages, $type = null, $style = null, $prefix = ' ', $padding = true, $escape = true); 146 | 147 | ``` 148 | 149 | 150 | ### Symfony progress bar 151 | 152 | ```php 153 | $items = range(1,10); 154 | 155 | $bar = $this->output->createProgressBar(count($items)); 156 | 157 | // Custom format 158 | $bar->setFormat('%message%' . PHP_EOL . '%bar% %percent:3s% %' . PHP_EOL . 'time: %elapsed:6s%/%estimated:-6s%' . PHP_EOL.PHP_EOL); 159 | $bar->setBarCharacter(''.$bar->getBarCharacter().''); 160 | $bar->setBarWidth(80); 161 | 162 | foreach ($items as $i) { 163 | sleep(1); 164 | $bar->advance(); 165 | $bar->setMessage("My bar, some progress... $i"); 166 | } 167 | 168 | $bar->finish(); 169 | ``` 170 | 171 | ## Custom formatter 172 | 173 | Register a custom OutputFormatterStyle 174 | ```php 175 | // in your init() 176 | \yii\base\Event::on( 177 | Commands::class, 178 | Commands::EVENT_BEFORE_ACTION, 179 | function (ActionEvent $event) { 180 | $style = new OutputFormatterStyle('white', 'cyan'); 181 | $event->action->output->getFormatter()->setStyle('ocean', $style); 182 | } 183 | ); 184 | ``` 185 | 186 | Apply the style 187 | ```php 188 | $this->title('Title in blue'); 189 | ``` 190 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ostark/yii2-artisan-bridge", 3 | "description": "Brings the ease of Artisan commands and the power of Symfony console to Yii2.", 4 | "type": "library", 5 | "keywords": [ 6 | "craftcms", 7 | "yii2", 8 | "artisan", 9 | "laravel", 10 | "symfony", 11 | "console" 12 | ], 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Oliver Stark", 17 | "homepage": "https://www.fortrabbit.com" 18 | } 19 | ], 20 | "support": { 21 | "email": "os@fortrabbit.com", 22 | "issues": "https://github.com/ostark/yii2-artisan-bridge/issues", 23 | "source": "https://github.com/ostark/yii2-artisan-bridge", 24 | "docs": "https://github.com/ostark/yii2-artisan-bridge" 25 | }, 26 | "require": { 27 | "yiisoft/yii2": "^2.0", 28 | "symfony/console": "^6.0", 29 | "symfony/process": "^3.0 || ^4.0 || ^5.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "ostark\\Yii2ArtisanBridge\\": "src/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ActionGroup.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->helpSummary = $summary; 22 | } 23 | 24 | public function setActions(array $actions = []): ActionGroup 25 | { 26 | $this->actions = $actions; 27 | 28 | return $this; 29 | } 30 | 31 | public function setOptions(array $options = []): ActionGroup 32 | { 33 | $this->optionAliases = $options; 34 | 35 | return $this; 36 | 37 | } 38 | 39 | 40 | public function setDefaultAction(string $name): ActionGroup 41 | { 42 | if (!isset($this->getActions()[$name])) { 43 | throw new \InvalidArgumentException("Unknown action name '$name'"); 44 | } 45 | 46 | $this->defaultAction = $name; 47 | 48 | return $this; 49 | 50 | } 51 | 52 | 53 | public function getActions(): array 54 | { 55 | return $this->actions; 56 | } 57 | 58 | public function getOptions(): array 59 | { 60 | return $this->optionAliases; 61 | } 62 | 63 | public function getHelpSummary(): string 64 | { 65 | return $this->helpSummary; 66 | } 67 | 68 | public function getDefaultAction(): string 69 | { 70 | return $this->defaultAction; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Bridge.php: -------------------------------------------------------------------------------- 1 | register(); 13 | 14 | \Yii::$app->set('errorHandler', $handler); 15 | 16 | \Yii::$app->controllerMap[$group->name] = [ 17 | 'class' => Commands::class, 18 | 'actions' => $group->getActions(), 19 | 'optionAliases' => $group->getOptions(), 20 | 'helpSummary' => $group->getHelpSummary(), 21 | 'defaultAction' => $group->getDefaultAction() 22 | ]; 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ConsoleOutput.php: -------------------------------------------------------------------------------- 1 | render($exception->getMessage(), [get_class($exception)]); 28 | } 29 | 30 | // Debug mode 31 | $messages = [ 32 | get_class($exception), 33 | "in " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . 34 | basename($exception->getFile()) . ": " . $exception->getLine() 35 | ]; 36 | 37 | if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) { 38 | $messages[] = print_r($exception->errorInfo, true); 39 | } 40 | 41 | return $this->render($exception->getMessage(), $messages, $exception->getTraceAsString()); 42 | 43 | } 44 | 45 | /** 46 | * @param string $title 47 | * @param array $messages 48 | * @param string|null $trace 49 | * 50 | * @return string|void 51 | */ 52 | protected function render(string $title, array $messages, string $trace = null) 53 | { 54 | $output = new OutputStyle(new ArgvInput(), new ConsoleOutput()); 55 | $messages = array_merge(["$title"], $messages); 56 | 57 | $output->block($messages, 'ERROR', 'fg=white;bg=red', ' ', true, false); 58 | 59 | if ($trace) { 60 | $trace = str_replace(\Yii::getAlias('@root'), '', $trace); 61 | $trace = "Stack trace:" . PHP_EOL . PHP_EOL . $trace; 62 | $output->block($trace, null, 'fg=white', '▓ ', true, false); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/OutputStyle.php: -------------------------------------------------------------------------------- 1 | write($prepend); 22 | } 23 | 24 | $chars = str_split($command); 25 | 26 | foreach ($chars as $char) { 27 | 28 | $this->write(sprintf("<%s>%s", $style, $char)); 29 | 30 | if (is_int($speed) && $speed > 0) { 31 | $delay = rand(10000, 1000000) / $speed; 32 | usleep($delay); 33 | } 34 | } 35 | 36 | // delay before new line 37 | usleep(1000000 / $speed); 38 | $this->newLine(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/base/Action.php: -------------------------------------------------------------------------------- 1 | input = new ArgvInput(); 53 | $this->input->setInteractive($controller->interactive); 54 | $this->output = new OutputStyle($this->input, new ConsoleOutput()); 55 | 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/base/ArtisanOutputTrait.php: -------------------------------------------------------------------------------- 1 | OutputInterface::VERBOSITY_VERBOSE, 37 | 'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE, 38 | 'vvv' => OutputInterface::VERBOSITY_DEBUG, 39 | 'quiet' => OutputInterface::VERBOSITY_QUIET, 40 | 'normal' => OutputInterface::VERBOSITY_NORMAL, 41 | ]; 42 | 43 | 44 | /** 45 | * Confirm a question with the user. 46 | * 47 | * @param string $question 48 | * @param bool $default 49 | * @return bool 50 | */ 51 | public function confirm($question, $default = false) 52 | { 53 | return $this->output->confirm($question, $default); 54 | } 55 | 56 | /** 57 | * Prompt the user for input. 58 | * 59 | * @param string $question 60 | * @param string|null $default 61 | * 62 | * @return string 63 | */ 64 | public function ask($question, $default = null) 65 | { 66 | return $this->output->ask($question, $default); 67 | } 68 | 69 | /** 70 | * Prompt the user for input with auto completion. 71 | * 72 | * @param string $question 73 | * @param array $choices 74 | * @param string|null $default 75 | * 76 | * @return string 77 | */ 78 | public function anticipate($question, array $choices, $default = null) 79 | { 80 | return $this->askWithCompletion($question, $choices, $default); 81 | } 82 | 83 | /** 84 | * Prompt the user for input with auto completion. 85 | * 86 | * @param string $question 87 | * @param array $choices 88 | * @param string|null $default 89 | * 90 | * @return string 91 | */ 92 | public function askWithCompletion($question, array $choices, $default = null) 93 | { 94 | $question = new Question($question, $default); 95 | $question->setAutocompleterValues($choices); 96 | 97 | return $this->output->askQuestion($question); 98 | } 99 | 100 | /** 101 | * Prompt the user for input but hide the answer from the console. 102 | * 103 | * @param string $question 104 | * @param bool $fallback 105 | * 106 | * @return string 107 | */ 108 | public function secret($question, $fallback = true) 109 | { 110 | $question = new Question($question); 111 | $question->setHidden(true)->setHiddenFallback($fallback); 112 | 113 | return $this->output->askQuestion($question); 114 | } 115 | 116 | /** 117 | * Give the user a single choice from an array of answers. 118 | * 119 | * @param string $question 120 | * @param array $choices 121 | * @param string|null $default 122 | * @param mixed|null $attempts 123 | * @param bool $multiple 124 | * 125 | * @return string 126 | */ 127 | public function choice($question, array $choices, $default = null, $attempts = null, $multiple = false) 128 | { 129 | $question = new ChoiceQuestion($question, $choices, $default); 130 | $question->setMaxAttempts($attempts)->setMultiselect($multiple); 131 | 132 | return $this->output->askQuestion($question); 133 | } 134 | 135 | /** 136 | * Format input to textual table. 137 | * 138 | * @param $headers 139 | * @param $rows 140 | * @param string $tableStyle 141 | * @param array $columnStyles 142 | */ 143 | public function table($headers, $rows, $tableStyle = 'default', array $columnStyles = []) 144 | { 145 | $table = new Table($this->output); 146 | if ($rows instanceof Arrayable) { 147 | $rows = $rows->toArray(); 148 | } 149 | $table->setHeaders((array)$headers)->setRows($rows)->setStyle($tableStyle); 150 | foreach ($columnStyles as $columnIndex => $columnStyle) { 151 | $table->setColumnStyle($columnIndex, $columnStyle); 152 | } 153 | $table->render(); 154 | } 155 | 156 | /** 157 | * Write a string as information output. 158 | * 159 | * @param string $string 160 | * @param null|int|string $verbosity 161 | * 162 | * @return void 163 | */ 164 | public function info($string, $verbosity = null) 165 | { 166 | $this->line($string, 'info', $verbosity); 167 | } 168 | 169 | /** 170 | * Write a string as standard output. 171 | * 172 | * @param string $string 173 | * @param string $style 174 | * @param null|int|string $verbosity 175 | * 176 | * @return void 177 | */ 178 | public function line($string, $style = null, $verbosity = null) 179 | { 180 | $styled = $style ? "<$style>$string" : $string; 181 | $this->output->writeln($styled, $this->parseVerbosity($verbosity)); 182 | } 183 | 184 | /** 185 | * Write a string as comment output. 186 | * 187 | * @param string $string 188 | * @param null|int|string $verbosity 189 | * 190 | * @return void 191 | */ 192 | public function comment($string, $verbosity = null) 193 | { 194 | $this->line($string, 'comment', $verbosity); 195 | } 196 | 197 | /** 198 | * Write a string as question output. 199 | * 200 | * @param string $string 201 | * @param null|int|string $verbosity 202 | * 203 | * @return void 204 | */ 205 | public function question($string, $verbosity = null) 206 | { 207 | $this->line($string, 'question', $verbosity); 208 | } 209 | 210 | /** 211 | * Write a string as error output. 212 | * 213 | * @param string $string 214 | * @param null|int|string $verbosity 215 | * 216 | * @return void 217 | */ 218 | public function error($string, $verbosity = null) 219 | { 220 | $this->line($string, 'error', $verbosity); 221 | } 222 | 223 | /** 224 | * Write a string as warning output. 225 | * 226 | * @param string $string 227 | * @param null|int|string $verbosity 228 | * 229 | * @return void 230 | */ 231 | public function warn($string, $verbosity = null) 232 | { 233 | if (!$this->output->getFormatter()->hasStyle('warning')) { 234 | $style = new OutputFormatterStyle('yellow'); 235 | $this->output->getFormatter()->setStyle('warning', $style); 236 | } 237 | $this->line($string, 'warning', $verbosity); 238 | } 239 | 240 | /** 241 | * Write a string in an alert box. 242 | * 243 | * @param string $string 244 | * 245 | * @return void 246 | */ 247 | public function alert($string) 248 | { 249 | $this->comment(str_repeat('*', strlen($string) + 12)); 250 | $this->comment('* ' . $string . ' *'); 251 | $this->comment(str_repeat('*', strlen($string) + 12)); 252 | $this->output->newLine(); 253 | } 254 | 255 | /** 256 | * Set the verbosity level. 257 | * 258 | * @param string|int $level 259 | * 260 | * @return void 261 | */ 262 | protected function setVerbosity($level) 263 | { 264 | $this->verbosity = $this->parseVerbosity($level); 265 | } 266 | 267 | /** 268 | * Get the verbosity level in terms of Symfony's OutputInterface level. 269 | * 270 | * @param string|int|null $level 271 | * 272 | * @return int 273 | */ 274 | protected function parseVerbosity($level = null) 275 | { 276 | if (isset($this->verbosityMap[$level])) { 277 | $level = $this->verbosityMap[$level]; 278 | } elseif (!is_int($level)) { 279 | $level = $this->verbosity; 280 | } 281 | 282 | return $level; 283 | } 284 | 285 | /** 286 | * Get the console command arguments. 287 | * 288 | * @return array 289 | */ 290 | protected function getArguments() 291 | { 292 | return []; 293 | } 294 | 295 | /** 296 | * Get the console command options. 297 | * 298 | * @return array 299 | */ 300 | protected function getOptions() 301 | { 302 | return []; 303 | } 304 | 305 | /** 306 | * Get the output implementation. 307 | * 308 | * @return \Symfony\Component\Console\Output\OutputInterface 309 | */ 310 | public function getOutput() 311 | { 312 | return $this->output; 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/base/BlockOutputTrait.php: -------------------------------------------------------------------------------- 1 | output->block($messages, $type, $style, $prefix, $padding, $escape); 28 | } 29 | 30 | /** 31 | * @param $message 32 | */ 33 | public function title($message) { 34 | $this->output->title($message); 35 | } 36 | 37 | /** 38 | * @param $message 39 | */ 40 | public function section($message) 41 | { 42 | $this->output->section($message); 43 | } 44 | 45 | /** 46 | * @param array $elements 47 | */ 48 | public function listing(array $elements) { 49 | $this->output->listing($elements); 50 | } 51 | 52 | /** 53 | * Formats a command comment. 54 | * 55 | * @param string|array $message 56 | */ 57 | public function commentBlock($message) 58 | { 59 | $this->block($message, null, null, ' // ', false, false); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function successBlock($message) 66 | { 67 | $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function errorBlock($message) 74 | { 75 | $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function warningBlock($message) 82 | { 83 | $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function noteBlock($message) 90 | { 91 | $this->block($message, 'NOTE', 'fg=yellow', ' ░ '); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function cautionBlock($message) 98 | { 99 | $this->block($message, 'CAUTION', 'fg=white;bg=red', '» ', true); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/base/Commands.php: -------------------------------------------------------------------------------- 1 | on(self::EVENT_BEFORE_ACTION, function (ActionEvent $event) { 40 | 41 | // Forward options to Action 42 | foreach (array_values($this->optionAliases) as $name) { 43 | if (in_array($name, array_values($this->optionAliases))) { 44 | if (isset($event->action->controller->options[$name])) { 45 | $event->action->$name = $event->action->controller->options[$name]; 46 | } 47 | } 48 | } 49 | 50 | // Forward $interactive always 51 | $event->action->interactive = $event->action->controller->interactive; 52 | 53 | }); 54 | } 55 | 56 | 57 | 58 | /** 59 | * Setup for actions and options 60 | * 61 | * @deprecated 1.2.0 No longer recommended, use Bridge::registerGroup() instead 62 | * 63 | * @param string $prefix 64 | * @param array $actions 65 | * @param array $options 66 | */ 67 | public static function register(string $prefix, array $actions = [], array $options = []) 68 | { 69 | $handler = new ErrorHandler(); 70 | $handler->register(); 71 | 72 | Yii::$app->set('errorHandler', $handler); 73 | 74 | Yii::$app->controllerMap[$prefix] = [ 75 | 'class' => get_called_class(), 76 | 'actions' => $actions, 77 | 'optionAliases' => $options 78 | ]; 79 | } 80 | 81 | /** 82 | * @deprecated 1.2.0 No longer recommended, use ActionGroup::setDefaultAction() instead 83 | * 84 | * @param string $prefix 85 | * @param string $name 86 | */ 87 | public static function setDefaultAction(string $prefix, string $name) 88 | { 89 | Yii::$app->controllerMap[$prefix]['defaultAction'] = $name; 90 | } 91 | 92 | /** 93 | * @return array 94 | */ 95 | public function actions() 96 | { 97 | return $this->actions; 98 | } 99 | 100 | public function optionAliases() 101 | { 102 | $defaultOptions = ['h' => 'help', 'i' => 'interactive']; 103 | 104 | return array_merge($defaultOptions, $this->optionAliases); 105 | } 106 | 107 | /** 108 | * Get options from public Action properties 109 | */ 110 | public function options($actionID) 111 | { 112 | $actionClass = $this->actions()[$actionID] ?? null; 113 | $action = new \ReflectionClass($actionClass); 114 | $actionOptions = []; 115 | 116 | foreach ($action->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { 117 | if (in_array($property->getName(), array_values($this->optionAliases()))) { 118 | $actionOptions[] = $property->getName(); 119 | } 120 | } 121 | 122 | return $actionOptions; 123 | 124 | } 125 | 126 | 127 | /** 128 | * Options getter 129 | * 130 | * @param string $name 131 | * 132 | * @return bool|mixed 133 | */ 134 | public function __get($name) 135 | { 136 | return null; 137 | } 138 | 139 | /** 140 | * Options setter 141 | * 142 | * @param string $name 143 | * @param mixed $value 144 | */ 145 | public function __set($name, $value) 146 | { 147 | $this->options[$name] = $value; 148 | } 149 | 150 | public function getHelpSummary() 151 | { 152 | return $this->helpSummary; 153 | } 154 | 155 | /** 156 | * Returns the help information for the options for the action. 157 | * 158 | * The returned value should be an array. The keys are the option names, and the values are 159 | * the corresponding help information. Each value must be an array of the following structure: 160 | * 161 | * - type: string, the PHP type of this argument. 162 | * - default: string, the default value of this argument 163 | * - comment: string, the comment of this argument 164 | * 165 | * The default implementation will return the help information extracted from the doc-comment of 166 | * the properties corresponding to the action options. 167 | * 168 | * @param Action $action 169 | * 170 | * @return array the help information of the action options 171 | * @throws \ReflectionException 172 | */ 173 | public function getActionOptionsHelp($action) 174 | { 175 | $optionNames = $this->options($action->id); 176 | if (empty($optionNames)) { 177 | return []; 178 | } 179 | 180 | $class = new \ReflectionClass($action); 181 | $options = []; 182 | 183 | foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { 184 | $name = $property->getName(); 185 | if (!in_array($name, $optionNames, true)) { 186 | continue; 187 | } 188 | $defaultValue = $property->getValue($action); 189 | $tags = $this->parseDocCommentTags($property); 190 | 191 | // Display camelCase options in kebab-case 192 | $name = Inflector::camel2id($name, '-', true); 193 | 194 | if (isset($tags['var']) || isset($tags['property'])) { 195 | $doc = isset($tags['var']) ? $tags['var'] : $tags['property']; 196 | if (is_array($doc)) { 197 | $doc = reset($doc); 198 | } 199 | if (preg_match('/^(\S+)(.*)/s', $doc, $matches)) { 200 | $type = $matches[1]; 201 | $comment = $matches[2]; 202 | } else { 203 | $type = null; 204 | $comment = $doc; 205 | } 206 | $options[$name] = [ 207 | 'type' => $type, 208 | 'default' => $defaultValue, 209 | 'comment' => $comment, 210 | ]; 211 | } else { 212 | $options[$name] = [ 213 | 'type' => null, 214 | 'default' => $defaultValue, 215 | 'comment' => '', 216 | ]; 217 | } 218 | } 219 | 220 | return $options; 221 | } 222 | 223 | } 224 | --------------------------------------------------------------------------------