Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/console/src/CommandReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class CommandReplacer
'name' => 'serve',
'description' => 'Start Hypervel servers',
],
'info' => null,
'server:watch' => null,
'gen:amqp-consumer' => 'make:amqp-consumer',
'gen:amqp-producer' => 'make:amqp-producer',
Expand Down
8 changes: 8 additions & 0 deletions src/foundation/src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ public function viewPath(string $path = ''): string
return $this->joinPaths($viewPath, $path);
}

/**
* Get the path to the storage directory.
*/
public function storagePath(string $path = ''): string
{
return $this->joinPaths($this->basePath('storage'), $path);
}

/**
* Join the given paths together.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/foundation/src/Bootstrap/RegisterFacades.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Hyperf\Collection\Arr;
use Hyperf\Contract\ConfigInterface;
use Hypervel\Foundation\Contracts\Application as ApplicationContract;
use Hypervel\Foundation\Support\Composer;
use Hypervel\Support\Composer;
use Hypervel\Support\Facades\Facade;
use Throwable;

Expand Down
2 changes: 1 addition & 1 deletion src/foundation/src/Bootstrap/RegisterProviders.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Hyperf\Contract\ConfigInterface;
use Hypervel\Foundation\Contracts\Application as ApplicationContract;
use Hypervel\Foundation\Providers\FoundationServiceProvider;
use Hypervel\Foundation\Support\Composer;
use Hypervel\Support\Composer;
use Throwable;

class RegisterProviders
Expand Down
2 changes: 1 addition & 1 deletion src/foundation/src/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Hyperf\Di\ScanHandler\PcntlScanHandler;
use Hyperf\Di\ScanHandler\ScanHandlerInterface;
use Hyperf\Support\DotenvManager;
use Hypervel\Foundation\Support\Composer;
use Hypervel\Support\Composer;

class ClassLoader
{
Expand Down
2 changes: 2 additions & 0 deletions src/foundation/src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Hyperf\Coordinator\Listener\ResumeExitCoordinatorListener;
use Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler;
use Hypervel\Console\ApplicationFactory;
use Hypervel\Foundation\Console\Commands\AboutCommand;
use Hypervel\Foundation\Console\Commands\ServerReloadCommand;
use Hypervel\Foundation\Console\Commands\VendorPublishCommand;
use Hypervel\Foundation\Exceptions\Contracts\ExceptionHandler as ExceptionHandlerContract;
Expand All @@ -29,6 +30,7 @@ public function __invoke(): array
ReloadDotenvAndConfig::class,
],
'commands' => [
AboutCommand::class,
ServerReloadCommand::class,
VendorPublishCommand::class,
],
Expand Down
191 changes: 191 additions & 0 deletions src/foundation/src/Console/Commands/AboutCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

declare(strict_types=1);

namespace Hypervel\Foundation\Console\Commands;

use Closure;
use Hyperf\Contract\ConfigInterface;
use Hypervel\Console\Command;
use Hypervel\Support\Collection;
use Hypervel\Support\Composer;
use Hypervel\Support\Str;
use Hypervel\Support\Stringable;

class AboutCommand extends Command
{
protected ?string $signature = 'about {--only= : The section to display}
{--json : Output the information as JSON}';

protected string $description = 'Display basic information about your application';

public function __construct(
protected ConfigInterface $config,
protected Composer $composer,
) {
parent::__construct();
}

public function handle()
{
$only = $this->sections();
$information = $this->gatherApplicationInformation();

if ($only = $this->sections()) {
$information = array_filter($information, function ($section) use ($only) {
return in_array($this->toSearchKeyword($section), $only);
}, ARRAY_FILTER_USE_KEY);
}

$this->display($information);
}

/**
* Display the application information.
*/
protected function display(array $information): void
{
$this->option('json')
? $this->displayJson($information)
: $this->displayDetail($information);
}

/**
* Display the application information as a detail view.
*/
protected function displayDetail(array $information): void
{
foreach ($information as $section => $data) {
$this->newLine();

$this->components->twoColumnDetail(' <fg=green;options=bold>' . $section . '</>');

foreach ($data as $key => $value) {
$this->components->twoColumnDetail($key, value($value, false));
}
}
}

/**
* Display the application information as JSON.
*/
protected function displayJson(array $information): void
{
$output = [];
foreach ($information as $section => $data) {
$section = $this->toSearchKeyword($section);
$output[$section] = array_map(function ($value, $key) {
return [
$this->toSearchKeyword($key) => value($value, true),
];
}, $data, array_keys($data));
}

$this->output->writeln(strip_tags(json_encode($output)));
}

/**
* Gather information about the application.
*/
protected function gatherApplicationInformation(): array
{
$data = [];

$formatEnabledStatus = fn ($value) => $value ? '<fg=yellow;options=bold>ENABLED</>' : 'OFF';
$formatCachedStatus = fn ($value) => $value ? '<fg=green;options=bold>CACHED</>' : '<fg=yellow;options=bold>NOT CACHED</>';

$data['Environment'] = [
'Application Name' => $this->config->get('app.name'),
'Hypervel Version' => $this->app->version(), /* @phpstan-ignore-line */
'PHP Version' => phpversion(),
'Swoole Version' => swoole_version(),
'Composer Version' => $this->composer->getVersion() ?? '<fg=yellow;options=bold>-</>',
'Environment' => $this->app->environment(), /* @phpstan-ignore-line */
'Debug Mode' => $this->format($this->config->get('app.debug'), console: $formatEnabledStatus),
'URL' => Str::of($this->config->get('app.url'))->replace(['http://', 'https://'], ''),
'Timezone' => $this->config->get('app.timezone'),
'Locale' => $this->config->get('app.locale'),
];

$data['Cache'] = [
'Runtime Proxy' => static::format($this->hasPhpFiles($this->app->basePath('runtime/container'), 'cache'), console: $formatCachedStatus), /* @phpstan-ignore-line */
'Views' => static::format($this->hasPhpFiles($this->app->storagePath('framework/views')), console: $formatCachedStatus), /* @phpstan-ignore-line */
];

$data['Drivers'] = array_filter([
'Broadcasting' => $this->config->get('broadcasting.default'),
'Cache' => $this->config->get('cache.default'),
'Database' => $this->config->get('database.default'),
'Logs' => function ($json) {
$logChannel = $this->config->get('logging.default');

if ($this->config->get('logging.channels.' . $logChannel . '.driver') === 'stack') {
$secondary = new Collection($this->config->get('logging.channels.' . $logChannel . '.channels'));

return value(static::format(
value: $logChannel,
console: fn ($value) => '<fg=yellow;options=bold>' . $value . '</> <fg=gray;options=bold>/</> ' . $secondary->implode(', '),
json: fn () => $secondary->all(),
), $json);
}
$logs = $logChannel;

return $logs;
},
'Mail' => $this->config->get('mail.default'),
'Queue' => $this->config->get('queue.default'),
'Session' => $this->config->get('session.driver'),
]);

return $data;
}

/**
* Determine whether the given directory has PHP files.
*/
protected function hasPhpFiles(string $path, string $extension = 'php'): bool
{
return count(glob($path . "/*.{$extension}")) > 0;
}

/**
* Get the sections provided to the command.
*/
protected function sections(): array
{
return (new Collection(explode(',', $this->option('only') ?? '')))
->filter()
->map(fn ($only) => $this->toSearchKeyword($only))
->all();
}

/**
* Materialize a function that formats a given value for CLI or JSON output.
*
* @param mixed $value
* @param null|(Closure(mixed):(mixed)) $console
* @param null|(Closure(mixed):(mixed)) $json
* @return Closure(bool):mixed
*/
protected function format($value, ?Closure $console = null, ?Closure $json = null): mixed
{
return function ($isJson) use ($value, $console, $json) {
if ($isJson === true && $json instanceof Closure) {
return value($json, $value);
}
if ($isJson === false && $console instanceof Closure) {
return value($console, $value);
}

return value($value);
};
}

/**
* Format the given string for searching.
*/
protected function toSearchKeyword(string $value): string
{
return (new Stringable($value))->lower()->snake()->value();
}
}
5 changes: 5 additions & 0 deletions src/foundation/src/Contracts/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public function resourcePath(string $path = ''): string;
*/
public function viewPath(string $path = ''): string;

/**
* Get the path to the storage directory.
*/
public function storagePath(string $path = ''): string;

/**
* Join the given paths together.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

declare(strict_types=1);

namespace Hypervel\Foundation\Support;
namespace Hypervel\Support;

use Composer\Autoload\ClassLoader;
use Hyperf\Collection\Collection;
use Hypervel\Filesystem\Filesystem;
use RuntimeException;
use Symfony\Component\Process\Process;

class Composer
{
Expand Down Expand Up @@ -181,6 +183,59 @@ public static function getBasePath(): string
return static::$basePath ?: BASE_PATH;
}

/**
* Get the Composer binary / command for the environment.
*/
public static function findComposer(?string $composerBinary = null): array
{
$filesystem = new Filesystem();
if (! is_null($composerBinary) && $filesystem->exists($composerBinary)) {
return [static::phpBinary(), $composerBinary];
}
if ($filesystem->exists(getcwd() . '/composer.phar')) {
return [static::phpBinary(), 'composer.phar'];
}

return ['composer'];
}

/**
* Get a new Symfony process instance.
*/
protected static function getProcess(array $command, array $env = []): Process
{
return (new Process($command, null, $env))
->setTimeout(null);
}

/**
* Get the PHP binary.
*/
protected static function phpBinary(): string
{
return php_binary();
}

/**
* Get the version of Composer.
*/
public static function getVersion(): ?string
{
$command = array_merge(static::findComposer(), ['-V', '--no-ansi']);

$process = static::getProcess($command);

$process->run();

$output = $process->getOutput();

if (preg_match('/(\d+(\.\d+){2})/', $output, $version)) {
return $version[1];
}

return explode(' ', $output)[2] ?? null;
}

protected static function reset(): void
{
static::$content = null;
Expand Down
2 changes: 1 addition & 1 deletion tests/Foundation/Bootstrap/RegisterFacadesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Hyperf\Contract\ConfigInterface;
use Hypervel\Foundation\Bootstrap\RegisterFacades;
use Hypervel\Foundation\Support\Composer;
use Hypervel\Support\Composer;
use Hypervel\Tests\Foundation\Concerns\HasMockedApplication;
use Hypervel\Tests\TestCase;
use Mockery as m;
Expand Down
2 changes: 1 addition & 1 deletion tests/Foundation/Bootstrap/RegisterProvidersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Hyperf\Contract\ConfigInterface;
use Hypervel\Foundation\Bootstrap\RegisterProviders;
use Hypervel\Foundation\Support\Composer;
use Hypervel\Support\Composer;
use Hypervel\Support\ServiceProvider;
use Hypervel\Tests\Foundation\Concerns\HasMockedApplication;
use Hypervel\Tests\TestCase;
Expand Down