1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Gerard van Helden <[email protected]> |
4
|
|
|
* @copyright Zicht Online <http://zicht.nl> |
5
|
|
|
*/ |
6
|
|
|
namespace Zicht\Tool; |
7
|
|
|
|
8
|
|
|
use Symfony\Component\Console\Application as BaseApplication; |
9
|
|
|
use Symfony\Component\Console\Input\ArrayInput; |
10
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
11
|
|
|
use Symfony\Component\Console\Input\InputOption; |
12
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
13
|
|
|
use Symfony\Component\Console\Input\InputDefinition; |
14
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
15
|
|
|
use Zicht\Version\Version; |
16
|
|
|
use Zicht\Tool\Command as Cmd; |
17
|
|
|
use Zicht\Tool\Configuration\ConfigurationLoader; |
18
|
|
|
use Zicht\Tool\Container\Container; |
19
|
|
|
use Zicht\Tool\Container\ContainerCompiler; |
20
|
|
|
use Zicht\Tool\Container\VerboseException; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Z CLI Application |
24
|
|
|
*/ |
25
|
|
|
class Application extends BaseApplication |
26
|
|
|
{ |
27
|
|
|
public static $HEADER = <<<EOSTR |
28
|
|
|
.------------. |
29
|
|
|
| ____ | |
30
|
|
|
| |__ | | |
31
|
|
|
| / / | |
32
|
|
|
| / /_ | |
33
|
|
|
| |____| | |
34
|
|
|
| ------ | |
35
|
|
|
'------------' |
36
|
|
|
EOSTR; |
37
|
|
|
|
38
|
|
|
protected $container = null; |
39
|
|
|
protected $loader = null; |
40
|
|
|
protected $plugins = array(); |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Construct the application with the specified name, version and config loader. |
45
|
|
|
* |
46
|
|
|
* @param string $name |
47
|
|
|
* @param Version $version |
48
|
|
|
* @param Configuration\ConfigurationLoader $loader |
49
|
|
|
*/ |
50
|
1 |
|
public function __construct($name = null, Version $version = null, ConfigurationLoader $loader = null) |
51
|
|
|
{ |
52
|
1 |
|
parent::__construct($name, (string)$version); |
53
|
1 |
|
$this->setDefaultCommand('z:list'); |
54
|
1 |
|
$this->loader = $loader; |
55
|
1 |
|
} |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Custom exception rendering, renders only the exception types and messages, hierarchically, but with regular |
60
|
|
|
* formatting if verbosity is higher. |
61
|
|
|
* |
62
|
|
|
* @param \Exception $e |
63
|
|
|
* @param \Symfony\Component\Console\Output\OutputInterface $output |
64
|
|
|
* @return void |
65
|
|
|
*/ |
66
|
|
|
public function renderException($e, $output) |
67
|
|
|
{ |
68
|
|
|
if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) { |
69
|
|
|
parent::renderException($e, $output); |
70
|
|
|
} else { |
71
|
|
|
/** @var $ancestry \Exception[] */ |
72
|
|
|
$ancestry = array(); |
73
|
|
|
$maxLength = 0; |
74
|
|
|
do { |
75
|
|
|
$ancestry[] = $e; |
76
|
|
|
$maxLength = max($maxLength, strlen(get_class($e))); |
77
|
|
|
$last = $e; |
78
|
|
|
} while ($e = $e->getPrevious()); |
79
|
|
|
|
80
|
|
|
if ($last instanceof VerboseException) { |
81
|
|
|
$last->output($output); |
82
|
|
|
} else { |
83
|
|
|
$depth = 0; |
84
|
|
|
foreach ($ancestry as $e) { |
85
|
|
|
$output->writeln( |
86
|
|
|
sprintf( |
87
|
|
|
'%s%-40s %s', |
88
|
|
|
($depth > 0 ? str_repeat(' ', $depth - 1) . '-> ' : ''), |
89
|
|
|
'<fg=red;options=bold>' . $e->getMessage() . '</fg=red;options=bold>', |
90
|
|
|
$depth == count($ancestry) - 1 ? str_pad('[' . get_class($e) . ']', $maxLength + 15, ' ') : '' |
91
|
|
|
) |
92
|
|
|
); |
93
|
|
|
$depth++; |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
$output->writeln('[' . join('::', Debug::$scope) . ']'); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set the container instance |
103
|
|
|
* |
104
|
|
|
* @param Container $container |
105
|
|
|
* @return void |
106
|
|
|
*/ |
107
|
1 |
|
public function setContainer(Container $container) |
108
|
|
|
{ |
109
|
1 |
|
$this->container = $container; |
110
|
1 |
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Returns the container instance, and initializes it if not yet available. |
115
|
|
|
* |
116
|
|
|
* @param bool $forceRecompile |
117
|
|
|
* @return Container |
118
|
|
|
*/ |
119
|
1 |
|
public function getContainer($forceRecompile = false) |
120
|
|
|
{ |
121
|
1 |
|
if (null === $this->container) { |
122
|
|
|
$config = $this->loader->processConfiguration(); |
123
|
|
|
$config['z']['sources'] = $this->loader->getSourceFiles(); |
124
|
|
|
$config['z']['cache_file'] = sys_get_temp_dir() . '/z_' . sha1(json_encode($this->loader->getSourceFiles())) . '.php'; |
125
|
|
|
if ($forceRecompile && is_file($config['z']['cache_file'])) { |
126
|
|
|
unlink($config['z']['cache_file']); |
127
|
|
|
clearstatcache(); |
128
|
|
|
} |
129
|
|
|
$compiler = new ContainerCompiler( |
130
|
|
|
$config, |
131
|
|
|
$this->loader->getPlugins(), |
132
|
|
|
$config['z']['cache_file'] |
133
|
|
|
); |
134
|
|
|
$this->container = $compiler->getContainer(); |
135
|
|
|
} |
136
|
1 |
|
return $this->container; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
|
140
|
1 |
|
protected function getDefaultCommands() |
141
|
|
|
{ |
142
|
|
|
return array( |
|
|
|
|
143
|
1 |
|
new Cmd\ListCommand(), |
144
|
1 |
|
new Cmd\HelpCommand(), |
145
|
1 |
|
new Cmd\EvalCommand(), |
146
|
1 |
|
new Cmd\DumpCommand(), |
147
|
1 |
|
new Cmd\InfoCommand() |
148
|
1 |
|
); |
149
|
|
|
} |
150
|
|
|
|
151
|
1 |
|
protected function getDefaultInputDefinition() |
152
|
|
|
{ |
153
|
1 |
|
return new InputDefinition( |
154
|
|
|
array( |
155
|
1 |
|
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), |
156
|
|
|
|
157
|
1 |
|
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'), |
158
|
1 |
|
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), |
159
|
1 |
|
new InputOption('--no-cache', '-c', InputOption::VALUE_NONE, 'Force recompilation of container code'), |
160
|
1 |
|
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.'), |
161
|
1 |
|
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), |
162
|
|
|
) |
163
|
1 |
|
); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @{inheritDoc} |
169
|
|
|
*/ |
170
|
|
|
public function doRun(InputInterface $input, OutputInterface $output) |
171
|
|
|
{ |
172
|
|
|
set_error_handler(new ErrorHandler($input, $output), E_USER_WARNING | E_USER_NOTICE | E_USER_DEPRECATED | E_RECOVERABLE_ERROR); |
173
|
|
|
|
174
|
|
|
$output->setFormatter(new Output\PrefixFormatter($output->getFormatter())); |
175
|
|
|
|
176
|
|
|
if (true === $input->hasParameterOption(array('--quiet', '-q'))) { |
177
|
|
|
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET); |
178
|
|
|
} elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) { |
179
|
|
|
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->plugins = array(); |
183
|
|
|
|
184
|
|
|
if ($input->hasParameterOption('--plugin')) { |
185
|
|
|
$value = array_filter(array_map('trim', explode(',', $input->getParameterOption('--plugin')))); |
186
|
|
|
|
187
|
|
|
foreach ($value as $name) { |
188
|
|
|
$this->loader->addPlugin($name); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
Debug::enterScope('init'); |
193
|
|
|
$container = $this->getContainer($input->hasParameterOption(array('--no-cache', '-c'))); |
194
|
|
|
|
195
|
|
|
$container->output = $output; |
196
|
|
|
|
197
|
|
|
$container->set('VERBOSE', $input->hasParameterOption(array('--verbose', '-v'))); |
198
|
|
|
$container->set('FORCE', $input->hasParameterOption(array('--force', '-f'))); |
199
|
|
|
$container->set('EXPLAIN', $input->hasParameterOption(array('--explain'))); |
200
|
|
|
$container->set('DEBUG', $input->hasParameterOption(array('--debug'))); |
201
|
|
|
|
202
|
|
|
foreach ($container->getCommands() as $task) { |
203
|
|
|
$this->add($task); |
|
|
|
|
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
Debug::exitScope('init'); |
207
|
|
|
|
208
|
|
|
Debug::enterScope('run'); |
209
|
|
|
if (true === $input->hasParameterOption(array('--help', '-h'))) { |
210
|
|
|
if (!$this->getCommandName($input)) { |
211
|
|
|
$input = new ArrayInput(array('command' => 'z:list')); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$ret = parent::doRun($input, $output); |
216
|
|
|
Debug::exitScope('run'); |
217
|
|
|
|
218
|
|
|
return $ret; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* @{inheritDoc} |
223
|
|
|
*/ |
224
|
|
|
public function getHelp() |
225
|
|
|
{ |
226
|
|
|
$ret = parent::getHelp(); |
227
|
|
|
if (self::$HEADER) { |
228
|
|
|
$ret = self::$HEADER . PHP_EOL . PHP_EOL . $ret; |
229
|
|
|
} |
230
|
|
|
return $ret; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
public function get($name) |
234
|
|
|
{ |
235
|
|
|
// The 'help' name can not be overridden as it's hard coded in the base class. |
236
|
|
|
if ('help' === $name) { |
237
|
|
|
$name = 'z:help'; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
return parent::get($name); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
|
244
|
|
|
|
245
|
|
|
} |
246
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.