Skip to content

Commit 54cee70

Browse files
committed
Merge branch 'main' of github.com:hypervel/components
2 parents 058df32 + 7866715 commit 54cee70

File tree

11 files changed

+268
-6
lines changed

11 files changed

+268
-6
lines changed

src/console/src/CommandReplacer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CommandReplacer
1313
'name' => 'serve',
1414
'description' => 'Start Hypervel servers',
1515
],
16+
'info' => null,
1617
'server:watch' => null,
1718
'gen:amqp-consumer' => 'make:amqp-consumer',
1819
'gen:amqp-producer' => 'make:amqp-producer',

src/foundation/src/Application.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ public function viewPath(string $path = ''): string
203203
return $this->joinPaths($viewPath, $path);
204204
}
205205

206+
/**
207+
* Get the path to the storage directory.
208+
*/
209+
public function storagePath(string $path = ''): string
210+
{
211+
return $this->joinPaths($this->basePath('storage'), $path);
212+
}
213+
206214
/**
207215
* Join the given paths together.
208216
*/

src/foundation/src/Bootstrap/RegisterFacades.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Hyperf\Collection\Arr;
88
use Hyperf\Contract\ConfigInterface;
99
use Hypervel\Foundation\Contracts\Application as ApplicationContract;
10-
use Hypervel\Foundation\Support\Composer;
10+
use Hypervel\Support\Composer;
1111
use Hypervel\Support\Facades\Facade;
1212
use Throwable;
1313

src/foundation/src/Bootstrap/RegisterProviders.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Hyperf\Contract\ConfigInterface;
99
use Hypervel\Foundation\Contracts\Application as ApplicationContract;
1010
use Hypervel\Foundation\Providers\FoundationServiceProvider;
11-
use Hypervel\Foundation\Support\Composer;
11+
use Hypervel\Support\Composer;
1212
use Throwable;
1313

1414
class RegisterProviders

src/foundation/src/ClassLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Hyperf\Di\ScanHandler\PcntlScanHandler;
1111
use Hyperf\Di\ScanHandler\ScanHandlerInterface;
1212
use Hyperf\Support\DotenvManager;
13-
use Hypervel\Foundation\Support\Composer;
13+
use Hypervel\Support\Composer;
1414

1515
class ClassLoader
1616
{

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+
}

src/foundation/src/Contracts/Application.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public function resourcePath(string $path = ''): string;
5656
*/
5757
public function viewPath(string $path = ''): string;
5858

59+
/**
60+
* Get the path to the storage directory.
61+
*/
62+
public function storagePath(string $path = ''): string;
63+
5964
/**
6065
* Join the given paths together.
6166
*/
Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
declare(strict_types=1);
44

5-
namespace Hypervel\Foundation\Support;
5+
namespace Hypervel\Support;
66

77
use Composer\Autoload\ClassLoader;
88
use Hyperf\Collection\Collection;
9+
use Hypervel\Filesystem\Filesystem;
910
use RuntimeException;
11+
use Symfony\Component\Process\Process;
1012

1113
class Composer
1214
{
@@ -181,6 +183,59 @@ public static function getBasePath(): string
181183
return static::$basePath ?: BASE_PATH;
182184
}
183185

186+
/**
187+
* Get the Composer binary / command for the environment.
188+
*/
189+
public static function findComposer(?string $composerBinary = null): array
190+
{
191+
$filesystem = new Filesystem();
192+
if (! is_null($composerBinary) && $filesystem->exists($composerBinary)) {
193+
return [static::phpBinary(), $composerBinary];
194+
}
195+
if ($filesystem->exists(getcwd() . '/composer.phar')) {
196+
return [static::phpBinary(), 'composer.phar'];
197+
}
198+
199+
return ['composer'];
200+
}
201+
202+
/**
203+
* Get a new Symfony process instance.
204+
*/
205+
protected static function getProcess(array $command, array $env = []): Process
206+
{
207+
return (new Process($command, null, $env))
208+
->setTimeout(null);
209+
}
210+
211+
/**
212+
* Get the PHP binary.
213+
*/
214+
protected static function phpBinary(): string
215+
{
216+
return php_binary();
217+
}
218+
219+
/**
220+
* Get the version of Composer.
221+
*/
222+
public static function getVersion(): ?string
223+
{
224+
$command = array_merge(static::findComposer(), ['-V', '--no-ansi']);
225+
226+
$process = static::getProcess($command);
227+
228+
$process->run();
229+
230+
$output = $process->getOutput();
231+
232+
if (preg_match('/(\d+(\.\d+){2})/', $output, $version)) {
233+
return $version[1];
234+
}
235+
236+
return explode(' ', $output)[2] ?? null;
237+
}
238+
184239
protected static function reset(): void
185240
{
186241
static::$content = null;

tests/Foundation/Bootstrap/RegisterFacadesTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use Hyperf\Contract\ConfigInterface;
88
use Hypervel\Foundation\Bootstrap\RegisterFacades;
9-
use Hypervel\Foundation\Support\Composer;
9+
use Hypervel\Support\Composer;
1010
use Hypervel\Tests\Foundation\Concerns\HasMockedApplication;
1111
use Hypervel\Tests\TestCase;
1212
use Mockery as m;

0 commit comments

Comments
 (0)