Skip to content

Commit 086a0a9

Browse files
committed
feat: implement about command
1 parent 1c2b665 commit 086a0a9

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

src/foundation/src/ConfigProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Hyperf\Coordinator\Listener\ResumeExitCoordinatorListener;
99
use Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler;
1010
use Hypervel\Console\ApplicationFactory;
11+
use Hypervel\Foundation\Console\Commands\AboutCommand;
1112
use Hypervel\Foundation\Console\Commands\ServerReloadCommand;
1213
use Hypervel\Foundation\Console\Commands\VendorPublishCommand;
1314
use Hypervel\Foundation\Exceptions\Contracts\ExceptionHandler as ExceptionHandlerContract;
@@ -29,6 +30,7 @@ public function __invoke(): array
2930
ReloadDotenvAndConfig::class,
3031
],
3132
'commands' => [
33+
AboutCommand::class,
3234
ServerReloadCommand::class,
3335
VendorPublishCommand::class,
3436
],
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Foundation\Console\Commands;
6+
7+
use Closure;
8+
use Hyperf\Contract\ConfigInterface;
9+
use Hypervel\Console\Command;
10+
use Hypervel\Support\Collection;
11+
use Hypervel\Support\Composer;
12+
use Hypervel\Support\Str;
13+
use Hypervel\Support\Stringable;
14+
15+
class AboutCommand extends Command
16+
{
17+
protected ?string $signature = 'about {--only= : The section to display}
18+
{--json : Output the information as JSON}';
19+
20+
protected string $description = 'Display basic information about your application';
21+
22+
public function __construct(
23+
protected ConfigInterface $config,
24+
protected Composer $composer,
25+
) {
26+
parent::__construct();
27+
}
28+
29+
public function handle()
30+
{
31+
$only = $this->sections();
32+
$information = $this->gatherApplicationInformation();
33+
34+
if ($only = $this->sections()) {
35+
$information = array_filter($information, function ($section) use ($only) {
36+
return in_array($this->toSearchKeyword($section), $only);
37+
}, ARRAY_FILTER_USE_KEY);
38+
}
39+
40+
$this->display($information);
41+
}
42+
43+
/**
44+
* Display the application information.
45+
*/
46+
protected function display(array $information): void
47+
{
48+
$this->option('json')
49+
? $this->displayJson($information)
50+
: $this->displayDetail($information);
51+
}
52+
53+
/**
54+
* Display the application information as a detail view.
55+
*/
56+
protected function displayDetail(array $information): void
57+
{
58+
foreach ($information as $section => $data) {
59+
$this->newLine();
60+
61+
$this->components->twoColumnDetail(' <fg=green;options=bold>' . $section . '</>');
62+
63+
foreach ($data as $key => $value) {
64+
$this->components->twoColumnDetail($key, value($value, false));
65+
}
66+
}
67+
}
68+
69+
/**
70+
* Display the application information as JSON.
71+
*/
72+
protected function displayJson(array $information): void
73+
{
74+
$output = [];
75+
foreach ($information as $section => $data) {
76+
$section = $this->toSearchKeyword($section);
77+
$output[$section] = array_map(function ($value, $key) {
78+
return [
79+
$this->toSearchKeyword($key) => value($value, true),
80+
];
81+
}, $data, array_keys($data));
82+
}
83+
84+
$this->output->writeln(strip_tags(json_encode($output)));
85+
}
86+
87+
/**
88+
* Gather information about the application.
89+
*/
90+
protected function gatherApplicationInformation(): array
91+
{
92+
$data = [];
93+
94+
$formatEnabledStatus = fn ($value) => $value ? '<fg=yellow;options=bold>ENABLED</>' : 'OFF';
95+
$formatCachedStatus = fn ($value) => $value ? '<fg=green;options=bold>CACHED</>' : '<fg=yellow;options=bold>NOT CACHED</>';
96+
97+
$data['Environment'] = [
98+
'Application Name' => $this->config->get('app.name'),
99+
'Hypervel Version' => $this->app->version(), /* @phpstan-ignore-line */
100+
'PHP Version' => phpversion(),
101+
'Swoole Version' => swoole_version(),
102+
'Composer Version' => $this->composer->getVersion() ?? '<fg=yellow;options=bold>-</>',
103+
'Environment' => $this->app->environment(), /* @phpstan-ignore-line */
104+
'Debug Mode' => $this->format($this->config->get('app.debug'), console: $formatEnabledStatus),
105+
'URL' => Str::of($this->config->get('app.url'))->replace(['http://', 'https://'], ''),
106+
'Timezone' => $this->config->get('app.timezone'),
107+
'Locale' => $this->config->get('app.locale'),
108+
];
109+
110+
$data['Cache'] = [
111+
'Runtime Proxy' => static::format($this->hasPhpFiles($this->app->basePath('runtime/container'), 'cache'), console: $formatCachedStatus), /* @phpstan-ignore-line */
112+
'Views' => static::format($this->hasPhpFiles($this->app->storagePath('framework/views')), console: $formatCachedStatus), /* @phpstan-ignore-line */
113+
];
114+
115+
$data['Drivers'] = array_filter([
116+
'Broadcasting' => $this->config->get('broadcasting.default'),
117+
'Cache' => $this->config->get('cache.default'),
118+
'Database' => $this->config->get('database.default'),
119+
'Logs' => function ($json) {
120+
$logChannel = $this->config->get('logging.default');
121+
122+
if ($this->config->get('logging.channels.' . $logChannel . '.driver') === 'stack') {
123+
$secondary = new Collection($this->config->get('logging.channels.' . $logChannel . '.channels'));
124+
125+
return value(static::format(
126+
value: $logChannel,
127+
console: fn ($value) => '<fg=yellow;options=bold>' . $value . '</> <fg=gray;options=bold>/</> ' . $secondary->implode(', '),
128+
json: fn () => $secondary->all(),
129+
), $json);
130+
}
131+
$logs = $logChannel;
132+
133+
return $logs;
134+
},
135+
'Mail' => $this->config->get('mail.default'),
136+
'Queue' => $this->config->get('queue.default'),
137+
'Session' => $this->config->get('session.driver'),
138+
]);
139+
140+
return $data;
141+
}
142+
143+
/**
144+
* Determine whether the given directory has PHP files.
145+
*/
146+
protected function hasPhpFiles(string $path, string $extension = 'php'): bool
147+
{
148+
return count(glob($path . "/*.{$extension}")) > 0;
149+
}
150+
151+
/**
152+
* Get the sections provided to the command.
153+
*/
154+
protected function sections(): array
155+
{
156+
return (new Collection(explode(',', $this->option('only') ?? '')))
157+
->filter()
158+
->map(fn ($only) => $this->toSearchKeyword($only))
159+
->all();
160+
}
161+
162+
/**
163+
* Materialize a function that formats a given value for CLI or JSON output.
164+
*
165+
* @param mixed $value
166+
* @param null|(Closure(mixed):(mixed)) $console
167+
* @param null|(Closure(mixed):(mixed)) $json
168+
* @return Closure(bool):mixed
169+
*/
170+
protected function format($value, ?Closure $console = null, ?Closure $json = null): mixed
171+
{
172+
return function ($isJson) use ($value, $console, $json) {
173+
if ($isJson === true && $json instanceof Closure) {
174+
return value($json, $value);
175+
}
176+
if ($isJson === false && $console instanceof Closure) {
177+
return value($console, $value);
178+
}
179+
180+
return value($value);
181+
};
182+
}
183+
184+
/**
185+
* Format the given string for searching.
186+
*/
187+
protected function toSearchKeyword(string $value): string
188+
{
189+
return (new Stringable($value))->lower()->snake()->value();
190+
}
191+
}

0 commit comments

Comments
 (0)