├── .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$style>" : $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 |
--------------------------------------------------------------------------------