first commit
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\Cache\FactoriesCache;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Config\Optimize;
|
||||
use Kint\Kint;
|
||||
|
||||
/**
|
||||
* Check the Config values.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\ConfigCheckTest
|
||||
*/
|
||||
final class ConfigCheck extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'config:check';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check your Config values.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'config:check <classname>';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'classname' => 'The config classname to check. Short classname or FQCN.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
if (! isset($params[0])) {
|
||||
CLI::error('You must specify a Config classname.');
|
||||
CLI::write(' Usage: ' . $this->usage);
|
||||
CLI::write('Example: config:check App');
|
||||
CLI::write(' config:check \'CodeIgniter\Shield\Config\Auth\'');
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
/** @var class-string<BaseConfig> $class */
|
||||
$class = $params[0];
|
||||
|
||||
// Load Config cache if it is enabled.
|
||||
$configCacheEnabled = class_exists(Optimize::class)
|
||||
&& (new Optimize())->configCacheEnabled;
|
||||
if ($configCacheEnabled) {
|
||||
$factoriesCache = new FactoriesCache();
|
||||
$factoriesCache->load('config');
|
||||
}
|
||||
|
||||
$config = config($class);
|
||||
|
||||
if ($config === null) {
|
||||
CLI::error('No such Config class: ' . $class);
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
if (defined('KINT_DIR') && Kint::$enabled_mode !== false) {
|
||||
CLI::write($this->getKintD($config));
|
||||
} else {
|
||||
CLI::write(
|
||||
CLI::color($this->getVarDump($config), 'cyan')
|
||||
);
|
||||
}
|
||||
|
||||
CLI::newLine();
|
||||
$state = CLI::color($configCacheEnabled ? 'Enabled' : 'Disabled', 'green');
|
||||
CLI::write('Config Caching: ' . $state);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets object dump by Kint d()
|
||||
*/
|
||||
private function getKintD(object $config): string
|
||||
{
|
||||
ob_start();
|
||||
d($config);
|
||||
$output = ob_get_clean();
|
||||
|
||||
$output = trim($output);
|
||||
|
||||
$lines = explode("\n", $output);
|
||||
array_splice($lines, 0, 3);
|
||||
array_splice($lines, -3);
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets object dump by var_dump()
|
||||
*/
|
||||
private function getVarDump(object $config): string
|
||||
{
|
||||
ob_start();
|
||||
var_dump($config);
|
||||
$output = ob_get_clean();
|
||||
|
||||
return preg_replace(
|
||||
'!.*system/Commands/Utilities/ConfigCheck.php.*\n!u',
|
||||
'',
|
||||
$output
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Config\DotEnv;
|
||||
|
||||
/**
|
||||
* Command to display the current environment,
|
||||
* or set a new one in the `.env` file.
|
||||
*/
|
||||
final class Environment extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'env';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Retrieves the current environment, or set a new one.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'env [<environment>]';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'environment' => '[Optional] The new environment to set. If none is provided, this will print the current environment.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Allowed values for environment. `testing` is excluded
|
||||
* since spark won't work on it.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static array $knownTypes = [
|
||||
'production',
|
||||
'development',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
if ($params === []) {
|
||||
CLI::write(sprintf('Your environment is currently set as %s.', CLI::color($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, 'green')));
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$env = strtolower(array_shift($params));
|
||||
|
||||
if ($env === 'testing') {
|
||||
CLI::error('The "testing" environment is reserved for PHPUnit testing.', 'light_gray', 'red');
|
||||
CLI::error('You will not be able to run spark under a "testing" environment.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! in_array($env, self::$knownTypes, true)) {
|
||||
CLI::error(sprintf('Invalid environment type "%s". Expected one of "%s".', $env, implode('" and "', self::$knownTypes)), 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->writeNewEnvironmentToEnvFile($env)) {
|
||||
CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// force DotEnv to reload the new environment
|
||||
// however we cannot redefine the ENVIRONMENT constant
|
||||
putenv('CI_ENVIRONMENT');
|
||||
unset($_ENV['CI_ENVIRONMENT'], $_SERVER['CI_ENVIRONMENT']);
|
||||
(new DotEnv(ROOTPATH))->load();
|
||||
|
||||
CLI::write(sprintf('Environment is successfully changed to "%s".', $env), 'green');
|
||||
CLI::write('The ENVIRONMENT constant will be changed in the next script execution.');
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/4sSORp/1 for the regex in action
|
||||
*/
|
||||
private function writeNewEnvironmentToEnvFile(string $newEnv): bool
|
||||
{
|
||||
$baseEnv = ROOTPATH . 'env';
|
||||
$envFile = ROOTPATH . '.env';
|
||||
|
||||
if (! is_file($envFile)) {
|
||||
if (! is_file($baseEnv)) {
|
||||
CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow');
|
||||
CLI::write('It is impossible to write the new environment type.', 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
copy($baseEnv, $envFile);
|
||||
}
|
||||
|
||||
$pattern = preg_quote($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, '/');
|
||||
$pattern = sprintf('/^[#\s]*CI_ENVIRONMENT[=\s]+%s$/m', $pattern);
|
||||
|
||||
return file_put_contents(
|
||||
$envFile,
|
||||
preg_replace($pattern, "\nCI_ENVIRONMENT = {$newEnv}", file_get_contents($envFile), -1, $count)
|
||||
) !== false && $count > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Commands\Utilities\Routes\FilterCollector;
|
||||
|
||||
/**
|
||||
* Check filters for a route.
|
||||
*/
|
||||
class FilterCheck extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'filter:check';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check filters for a route.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'filter:check <HTTP method> <route>';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'method' => 'The HTTP method. GET, POST, PUT, etc.',
|
||||
'route' => 'The route (URI path) to check filters.',
|
||||
];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* @return int exit code
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$tbody = [];
|
||||
if (! isset($params[0], $params[1])) {
|
||||
CLI::error('You must specify a HTTP verb and a route.');
|
||||
CLI::write(' Usage: ' . $this->usage);
|
||||
CLI::write('Example: filter:check GET /');
|
||||
CLI::write(' filter:check PUT products/1');
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$method = $params[0];
|
||||
$route = $params[1];
|
||||
|
||||
// Load Routes
|
||||
service('routes')->loadRoutes();
|
||||
|
||||
$filterCollector = new FilterCollector();
|
||||
|
||||
$filters = $filterCollector->get($method, $route);
|
||||
|
||||
// PageNotFoundException
|
||||
if ($filters['before'] === ['<unknown>']) {
|
||||
CLI::error(
|
||||
"Can't find a route: " .
|
||||
CLI::color(
|
||||
'"' . strtoupper($method) . ' ' . $route . '"',
|
||||
'black',
|
||||
'light_gray'
|
||||
),
|
||||
);
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$filters = $this->addRequiredFilters($filterCollector, $filters);
|
||||
|
||||
$tbody[] = [
|
||||
strtoupper($method),
|
||||
$route,
|
||||
implode(' ', $filters['before']),
|
||||
implode(' ', $filters['after']),
|
||||
];
|
||||
|
||||
$thead = [
|
||||
'Method',
|
||||
'Route',
|
||||
'Before Filters',
|
||||
'After Filters',
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
private function addRequiredFilters(FilterCollector $filterCollector, array $filters): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
$required = $filterCollector->getRequiredFilters();
|
||||
|
||||
$colored = [];
|
||||
|
||||
foreach ($required['before'] as $filter) {
|
||||
$filter = CLI::color($filter, 'yellow');
|
||||
$colored[] = $filter;
|
||||
}
|
||||
$output['before'] = array_merge($colored, $filters['before']);
|
||||
|
||||
$colored = [];
|
||||
|
||||
foreach ($required['after'] as $filter) {
|
||||
$filter = CLI::color($filter, 'yellow');
|
||||
$colored[] = $filter;
|
||||
}
|
||||
$output['after'] = array_merge($filters['after'], $colored);
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use Config\Autoload;
|
||||
|
||||
/**
|
||||
* Lists namespaces set in Config\Autoload with their
|
||||
* full server path. Helps you to verify that you have
|
||||
* the namespaces setup correctly.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\NamespacesTest
|
||||
*/
|
||||
class Namespaces extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'namespaces';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Verifies your namespaces are setup correctly.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'namespaces';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-c' => 'Show only CodeIgniter config namespaces.',
|
||||
'-r' => 'Show raw path strings.',
|
||||
'-m' => 'Specify max length of the path strings to output. Default: 60.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$params['m'] = (int) ($params['m'] ?? 60);
|
||||
|
||||
$tbody = array_key_exists('c', $params) ? $this->outputCINamespaces($params) : $this->outputAllNamespaces($params);
|
||||
|
||||
$thead = [
|
||||
'Namespace',
|
||||
'Path',
|
||||
'Found?',
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
}
|
||||
|
||||
private function outputAllNamespaces(array $params): array
|
||||
{
|
||||
$maxLength = $params['m'];
|
||||
|
||||
$autoloader = service('autoloader');
|
||||
|
||||
$tbody = [];
|
||||
|
||||
foreach ($autoloader->getNamespace() as $ns => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
if (array_key_exists('r', $params)) {
|
||||
$pathOutput = $this->truncate($path, $maxLength);
|
||||
} else {
|
||||
$pathOutput = $this->truncate(clean_path($path), $maxLength);
|
||||
}
|
||||
|
||||
$tbody[] = [
|
||||
$ns,
|
||||
$pathOutput,
|
||||
is_dir($path) ? 'Yes' : 'MISSING',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tbody;
|
||||
}
|
||||
|
||||
private function truncate(string $string, int $max): string
|
||||
{
|
||||
$length = strlen($string);
|
||||
|
||||
if ($length > $max) {
|
||||
return substr($string, 0, $max - 3) . '...';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function outputCINamespaces(array $params): array
|
||||
{
|
||||
$maxLength = $params['m'];
|
||||
|
||||
$config = new Autoload();
|
||||
|
||||
$tbody = [];
|
||||
|
||||
foreach ($config->psr4 as $ns => $paths) {
|
||||
foreach ((array) $paths as $path) {
|
||||
if (array_key_exists('r', $params)) {
|
||||
$pathOutput = $this->truncate($path, $maxLength);
|
||||
} else {
|
||||
$pathOutput = $this->truncate(clean_path($path), $maxLength);
|
||||
}
|
||||
|
||||
$path = realpath($path) ?: $path;
|
||||
|
||||
$tbody[] = [
|
||||
$ns,
|
||||
$pathOutput,
|
||||
is_dir($path) ? 'Yes' : 'MISSING',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tbody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocator;
|
||||
use CodeIgniter\Autoloader\FileLocatorCached;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Optimize for production.
|
||||
*/
|
||||
final class Optimize extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'optimize';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Optimize for production.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'optimize';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
try {
|
||||
$this->enableCaching();
|
||||
$this->clearCache();
|
||||
$this->removeDevPackages();
|
||||
} catch (RuntimeException) {
|
||||
CLI::error('The "spark optimize" failed.');
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
private function clearCache(): void
|
||||
{
|
||||
$locator = new FileLocatorCached(new FileLocator(service('autoloader')));
|
||||
$locator->deleteCache();
|
||||
CLI::write('Removed FileLocatorCache.', 'green');
|
||||
|
||||
$cache = WRITEPATH . 'cache/FactoriesCache_config';
|
||||
$this->removeFile($cache);
|
||||
}
|
||||
|
||||
private function removeFile(string $cache): void
|
||||
{
|
||||
if (is_file($cache)) {
|
||||
$result = unlink($cache);
|
||||
|
||||
if ($result) {
|
||||
CLI::write('Removed "' . clean_path($cache) . '".', 'green');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::error('Error in removing file: ' . clean_path($cache));
|
||||
|
||||
throw new RuntimeException(__METHOD__);
|
||||
}
|
||||
}
|
||||
|
||||
private function enableCaching(): void
|
||||
{
|
||||
$publisher = new Publisher(APPPATH, APPPATH);
|
||||
|
||||
$config = APPPATH . 'Config/Optimize.php';
|
||||
|
||||
$result = $publisher->replace(
|
||||
$config,
|
||||
[
|
||||
'public bool $configCacheEnabled = false;' => 'public bool $configCacheEnabled = true;',
|
||||
'public bool $locatorCacheEnabled = false;' => 'public bool $locatorCacheEnabled = true;',
|
||||
]
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
CLI::write(
|
||||
'Config Caching and FileLocator Caching are enabled in "app/Config/Optimize.php".',
|
||||
'green'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::error('Error in updating file: ' . clean_path($config));
|
||||
|
||||
throw new RuntimeException(__METHOD__);
|
||||
}
|
||||
|
||||
private function removeDevPackages(): void
|
||||
{
|
||||
if (! defined('VENDORPATH')) {
|
||||
return;
|
||||
}
|
||||
|
||||
chdir(ROOTPATH);
|
||||
passthru('composer install --no-dev', $status);
|
||||
|
||||
if ($status === 0) {
|
||||
CLI::write('Removed Composer dev packages.', 'green');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::error('Error in removing Composer dev packages.');
|
||||
|
||||
throw new RuntimeException(__METHOD__);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\Security\CheckPhpIni;
|
||||
|
||||
/**
|
||||
* Check php.ini values.
|
||||
*/
|
||||
final class PhpIniCheck extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'phpini:check';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check your php.ini values.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'phpini:check';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
CheckPhpIni::run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
|
||||
/**
|
||||
* Discovers all Publisher classes from the "Publishers/" directory
|
||||
* across namespaces. Executes `publish()` from each instance, parsing
|
||||
* each result.
|
||||
*/
|
||||
class Publish extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'publish';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Discovers and executes all predefined Publisher classes.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'publish [<directory>]';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'directory' => '[Optional] The directory to scan within each namespace. Default: "Publishers".',
|
||||
];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$directory = array_shift($params) ?? 'Publishers';
|
||||
|
||||
if ([] === $publishers = Publisher::discover($directory)) {
|
||||
CLI::write(lang('Publisher.publishMissing', [$directory]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($publishers as $publisher) {
|
||||
if ($publisher->publish()) {
|
||||
CLI::write(lang('Publisher.publishSuccess', [
|
||||
$publisher::class,
|
||||
count($publisher->getPublished()),
|
||||
$publisher->getDestination(),
|
||||
]), 'green');
|
||||
} else {
|
||||
CLI::error(lang('Publisher.publishFailure', [
|
||||
$publisher::class,
|
||||
$publisher->getDestination(),
|
||||
]), 'light_gray', 'red');
|
||||
|
||||
foreach ($publisher->getErrors() as $file => $exception) {
|
||||
CLI::write($file);
|
||||
CLI::error($exception->getMessage());
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Commands\Utilities\Routes\AutoRouteCollector;
|
||||
use CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved\AutoRouteCollector as AutoRouteCollectorImproved;
|
||||
use CodeIgniter\Commands\Utilities\Routes\FilterCollector;
|
||||
use CodeIgniter\Commands\Utilities\Routes\SampleURIGenerator;
|
||||
use CodeIgniter\Router\DefinedRouteCollector;
|
||||
use CodeIgniter\Router\Router;
|
||||
use Config\Feature;
|
||||
use Config\Routing;
|
||||
|
||||
/**
|
||||
* Lists all the routes. This will include any Routes files
|
||||
* that can be discovered, and will include routes that are not defined
|
||||
* in routes files, but are instead discovered through auto-routing.
|
||||
*/
|
||||
class Routes extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'routes';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Displays all routes.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'routes';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-h' => 'Sort by Handler.',
|
||||
'--host' => 'Specify hostname in request URI.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$sortByHandler = array_key_exists('h', $params);
|
||||
$host = $params['host'] ?? null;
|
||||
|
||||
// Set HTTP_HOST
|
||||
if ($host) {
|
||||
$request = service('request');
|
||||
$_SERVER = $request->getServer();
|
||||
$_SERVER['HTTP_HOST'] = $host;
|
||||
$request->setGlobal('server', $_SERVER);
|
||||
}
|
||||
|
||||
$collection = service('routes')->loadRoutes();
|
||||
|
||||
// Reset HTTP_HOST
|
||||
if ($host) {
|
||||
unset($_SERVER['HTTP_HOST']);
|
||||
}
|
||||
|
||||
$methods = Router::HTTP_METHODS;
|
||||
|
||||
$tbody = [];
|
||||
$uriGenerator = new SampleURIGenerator();
|
||||
$filterCollector = new FilterCollector();
|
||||
|
||||
$definedRouteCollector = new DefinedRouteCollector($collection);
|
||||
|
||||
foreach ($definedRouteCollector->collect() as $route) {
|
||||
$sampleUri = $uriGenerator->get($route['route']);
|
||||
$filters = $filterCollector->get($route['method'], $sampleUri);
|
||||
|
||||
$routeName = ($route['route'] === $route['name']) ? '»' : $route['name'];
|
||||
|
||||
$tbody[] = [
|
||||
strtoupper($route['method']),
|
||||
$route['route'],
|
||||
$routeName,
|
||||
$route['handler'],
|
||||
implode(' ', array_map(class_basename(...), $filters['before'])),
|
||||
implode(' ', array_map(class_basename(...), $filters['after'])),
|
||||
];
|
||||
}
|
||||
|
||||
if ($collection->shouldAutoRoute()) {
|
||||
$autoRoutesImproved = config(Feature::class)->autoRoutesImproved ?? false;
|
||||
|
||||
if ($autoRoutesImproved) {
|
||||
$autoRouteCollector = new AutoRouteCollectorImproved(
|
||||
$collection->getDefaultNamespace(),
|
||||
$collection->getDefaultController(),
|
||||
$collection->getDefaultMethod(),
|
||||
$methods,
|
||||
$collection->getRegisteredControllers('*')
|
||||
);
|
||||
|
||||
$autoRoutes = $autoRouteCollector->get();
|
||||
|
||||
// Check for Module Routes.
|
||||
if ($routingConfig = config(Routing::class)) {
|
||||
foreach ($routingConfig->moduleRoutes as $uri => $namespace) {
|
||||
$autoRouteCollector = new AutoRouteCollectorImproved(
|
||||
$namespace,
|
||||
$collection->getDefaultController(),
|
||||
$collection->getDefaultMethod(),
|
||||
$methods,
|
||||
$collection->getRegisteredControllers('*'),
|
||||
$uri
|
||||
);
|
||||
|
||||
$autoRoutes = [...$autoRoutes, ...$autoRouteCollector->get()];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$autoRouteCollector = new AutoRouteCollector(
|
||||
$collection->getDefaultNamespace(),
|
||||
$collection->getDefaultController(),
|
||||
$collection->getDefaultMethod()
|
||||
);
|
||||
|
||||
$autoRoutes = $autoRouteCollector->get();
|
||||
|
||||
foreach ($autoRoutes as &$routes) {
|
||||
// There is no `AUTO` method, but it is intentional not to get route filters.
|
||||
$filters = $filterCollector->get('AUTO', $uriGenerator->get($routes[1]));
|
||||
|
||||
$routes[] = implode(' ', array_map(class_basename(...), $filters['before']));
|
||||
$routes[] = implode(' ', array_map(class_basename(...), $filters['after']));
|
||||
}
|
||||
}
|
||||
|
||||
$tbody = [...$tbody, ...$autoRoutes];
|
||||
}
|
||||
|
||||
$thead = [
|
||||
'Method',
|
||||
'Route',
|
||||
'Name',
|
||||
$sortByHandler ? 'Handler ↓' : 'Handler',
|
||||
'Before Filters',
|
||||
'After Filters',
|
||||
];
|
||||
|
||||
// Sort by Handler.
|
||||
if ($sortByHandler) {
|
||||
usort($tbody, static fn ($handler1, $handler2) => strcmp($handler1[3], $handler2[3]));
|
||||
}
|
||||
|
||||
if ($host) {
|
||||
CLI::write('Host: ' . $host);
|
||||
}
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
|
||||
$this->showRequiredFilters();
|
||||
}
|
||||
|
||||
private function showRequiredFilters(): void
|
||||
{
|
||||
$filterCollector = new FilterCollector();
|
||||
|
||||
$required = $filterCollector->getRequiredFilters();
|
||||
|
||||
$filters = [];
|
||||
|
||||
foreach ($required['before'] as $filter) {
|
||||
$filters[] = CLI::color($filter, 'yellow');
|
||||
}
|
||||
|
||||
CLI::write('Required Before Filters: ' . implode(', ', $filters));
|
||||
|
||||
$filters = [];
|
||||
|
||||
foreach ($required['after'] as $filter) {
|
||||
$filters[] = CLI::color($filter, 'yellow');
|
||||
}
|
||||
|
||||
CLI::write(' Required After Filters: ' . implode(', ', $filters));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
/**
|
||||
* Collects data for auto route listing.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\AutoRouteCollectorTest
|
||||
*/
|
||||
final class AutoRouteCollector
|
||||
{
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
*/
|
||||
public function __construct(private readonly string $namespace, private readonly string $defaultController, private readonly string $defaultMethod)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<list<string>>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$finder = new ControllerFinder($this->namespace);
|
||||
$reader = new ControllerMethodReader($this->namespace);
|
||||
|
||||
$tbody = [];
|
||||
|
||||
foreach ($finder->find() as $class) {
|
||||
$output = $reader->read(
|
||||
$class,
|
||||
$this->defaultController,
|
||||
$this->defaultMethod
|
||||
);
|
||||
|
||||
foreach ($output as $item) {
|
||||
$tbody[] = [
|
||||
'auto',
|
||||
$item['route'],
|
||||
'',
|
||||
$item['handler'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tbody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved;
|
||||
|
||||
use CodeIgniter\Commands\Utilities\Routes\ControllerFinder;
|
||||
use CodeIgniter\Commands\Utilities\Routes\FilterCollector;
|
||||
|
||||
/**
|
||||
* Collects data for Auto Routing Improved.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved\AutoRouteCollectorTest
|
||||
*/
|
||||
final class AutoRouteCollector
|
||||
{
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
* @param list<class-string> $protectedControllers List of controllers in Defined
|
||||
* Routes that should not be accessed via Auto-Routing.
|
||||
* @param list<string> $httpMethods
|
||||
* @param string $prefix URI prefix for Module Routing
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $namespace,
|
||||
private readonly string $defaultController,
|
||||
private readonly string $defaultMethod,
|
||||
private readonly array $httpMethods,
|
||||
private readonly array $protectedControllers,
|
||||
private string $prefix = ''
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<list<string>>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$finder = new ControllerFinder($this->namespace);
|
||||
$reader = new ControllerMethodReader($this->namespace, $this->httpMethods);
|
||||
|
||||
$tbody = [];
|
||||
|
||||
foreach ($finder->find() as $class) {
|
||||
// Exclude controllers in Defined Routes.
|
||||
if (in_array('\\' . $class, $this->protectedControllers, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routes = $reader->read(
|
||||
$class,
|
||||
$this->defaultController,
|
||||
$this->defaultMethod
|
||||
);
|
||||
|
||||
if ($routes === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routes = $this->addFilters($routes);
|
||||
|
||||
foreach ($routes as $item) {
|
||||
$route = $item['route'] . $item['route_params'];
|
||||
|
||||
// For module routing
|
||||
if ($this->prefix !== '' && $route === '/') {
|
||||
$route = $this->prefix;
|
||||
} elseif ($this->prefix !== '') {
|
||||
$route = $this->prefix . '/' . $route;
|
||||
}
|
||||
|
||||
$tbody[] = [
|
||||
strtoupper($item['method']) . '(auto)',
|
||||
$route,
|
||||
'',
|
||||
$item['handler'],
|
||||
$item['before'],
|
||||
$item['after'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tbody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding Filters
|
||||
*
|
||||
* @param list<array<string, array|string>> $routes
|
||||
*
|
||||
* @return list<array<string, array|string>>
|
||||
*/
|
||||
private function addFilters($routes)
|
||||
{
|
||||
$filterCollector = new FilterCollector(true);
|
||||
|
||||
foreach ($routes as &$route) {
|
||||
$routePath = $route['route'];
|
||||
|
||||
// For module routing
|
||||
if ($this->prefix !== '' && $route === '/') {
|
||||
$routePath = $this->prefix;
|
||||
} elseif ($this->prefix !== '') {
|
||||
$routePath = $this->prefix . '/' . $routePath;
|
||||
}
|
||||
|
||||
// Search filters for the URI with all params
|
||||
$sampleUri = $this->generateSampleUri($route);
|
||||
$filtersLongest = $filterCollector->get($route['method'], $routePath . $sampleUri);
|
||||
|
||||
// Search filters for the URI without optional params
|
||||
$sampleUri = $this->generateSampleUri($route, false);
|
||||
$filtersShortest = $filterCollector->get($route['method'], $routePath . $sampleUri);
|
||||
|
||||
// Get common array elements
|
||||
$filters['before'] = array_intersect($filtersLongest['before'], $filtersShortest['before']);
|
||||
$filters['after'] = array_intersect($filtersLongest['after'], $filtersShortest['after']);
|
||||
|
||||
$route['before'] = implode(' ', array_map(class_basename(...), $filters['before']));
|
||||
$route['after'] = implode(' ', array_map(class_basename(...), $filters['after']));
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
private function generateSampleUri(array $route, bool $longest = true): string
|
||||
{
|
||||
$sampleUri = '';
|
||||
|
||||
if (isset($route['params'])) {
|
||||
$i = 1;
|
||||
|
||||
foreach ($route['params'] as $required) {
|
||||
if ($longest && ! $required) {
|
||||
$sampleUri .= '/' . $i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sampleUri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved;
|
||||
|
||||
use Config\Routing;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Reads a controller and returns a list of auto route listing.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved\ControllerMethodReaderTest
|
||||
*/
|
||||
final class ControllerMethodReader
|
||||
{
|
||||
private readonly bool $translateURIDashes;
|
||||
private readonly bool $translateUriToCamelCase;
|
||||
|
||||
/**
|
||||
* @param string $namespace the default namespace
|
||||
* @param list<string> $httpMethods
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $namespace,
|
||||
private readonly array $httpMethods
|
||||
) {
|
||||
$config = config(Routing::class);
|
||||
$this->translateURIDashes = $config->translateURIDashes;
|
||||
$this->translateUriToCamelCase = $config->translateUriToCamelCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns found route info in the controller.
|
||||
*
|
||||
* @param class-string $class
|
||||
*
|
||||
* @return list<array<string, array|string>>
|
||||
*/
|
||||
public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array
|
||||
{
|
||||
$reflection = new ReflectionClass($class);
|
||||
|
||||
if ($reflection->isAbstract()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$classname = $reflection->getName();
|
||||
$classShortname = $reflection->getShortName();
|
||||
|
||||
$output = [];
|
||||
$classInUri = $this->convertClassNameToUri($classname);
|
||||
|
||||
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
$methodName = $method->getName();
|
||||
|
||||
foreach ($this->httpMethods as $httpVerb) {
|
||||
if (str_starts_with($methodName, strtolower($httpVerb))) {
|
||||
// Remove HTTP verb prefix.
|
||||
$methodInUri = $this->convertMethodNameToUri($httpVerb, $methodName);
|
||||
|
||||
// Check if it is the default method.
|
||||
if ($methodInUri === $defaultMethod) {
|
||||
$routeForDefaultController = $this->getRouteForDefaultController(
|
||||
$classShortname,
|
||||
$defaultController,
|
||||
$classInUri,
|
||||
$classname,
|
||||
$methodName,
|
||||
$httpVerb,
|
||||
$method
|
||||
);
|
||||
|
||||
if ($routeForDefaultController !== []) {
|
||||
// The controller is the default controller. It only
|
||||
// has a route for the default method. Other methods
|
||||
// will not be routed even if they exist.
|
||||
$output = [...$output, ...$routeForDefaultController];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
[$params, $routeParams] = $this->getParameters($method);
|
||||
|
||||
// Route for the default method.
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $classInUri,
|
||||
'route_params' => $routeParams,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => $params,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$route = $classInUri . '/' . $methodInUri;
|
||||
|
||||
[$params, $routeParams] = $this->getParameters($method);
|
||||
|
||||
// If it is the default controller, the method will not be
|
||||
// routed.
|
||||
if ($classShortname === $defaultController) {
|
||||
$route = 'x ' . $route;
|
||||
}
|
||||
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $route,
|
||||
'route_params' => $routeParams,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function getParameters(ReflectionMethod $method): array
|
||||
{
|
||||
$params = [];
|
||||
$routeParams = '';
|
||||
$refParams = $method->getParameters();
|
||||
|
||||
foreach ($refParams as $param) {
|
||||
$required = true;
|
||||
if ($param->isOptional()) {
|
||||
$required = false;
|
||||
|
||||
$routeParams .= '[/..]';
|
||||
} else {
|
||||
$routeParams .= '/..';
|
||||
}
|
||||
|
||||
// [variable_name => required?]
|
||||
$params[$param->getName()] = $required;
|
||||
}
|
||||
|
||||
return [$params, $routeParams];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $classname
|
||||
*
|
||||
* @return string URI path part from the folder(s) and controller
|
||||
*/
|
||||
private function convertClassNameToUri(string $classname): string
|
||||
{
|
||||
// remove the namespace
|
||||
$pattern = '/' . preg_quote($this->namespace, '/') . '/';
|
||||
$class = ltrim(preg_replace($pattern, '', $classname), '\\');
|
||||
|
||||
$classParts = explode('\\', $class);
|
||||
$classPath = '';
|
||||
|
||||
foreach ($classParts as $part) {
|
||||
// make the first letter lowercase, because auto routing makes
|
||||
// the URI path's first letter uppercase and search the controller
|
||||
$classPath .= lcfirst($part) . '/';
|
||||
}
|
||||
|
||||
$classUri = rtrim($classPath, '/');
|
||||
|
||||
return $this->translateToUri($classUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string URI path part from the method
|
||||
*/
|
||||
private function convertMethodNameToUri(string $httpVerb, string $methodName): string
|
||||
{
|
||||
$methodUri = lcfirst(substr($methodName, strlen($httpVerb)));
|
||||
|
||||
return $this->translateToUri($methodUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string classname or method name
|
||||
*/
|
||||
private function translateToUri(string $string): string
|
||||
{
|
||||
if ($this->translateUriToCamelCase) {
|
||||
$string = strtolower(
|
||||
preg_replace('/([a-z\d])([A-Z])/', '$1-$2', $string)
|
||||
);
|
||||
} elseif ($this->translateURIDashes) {
|
||||
$string = str_replace('_', '-', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a route for the default controller.
|
||||
*
|
||||
* @return list<array>
|
||||
*/
|
||||
private function getRouteForDefaultController(
|
||||
string $classShortname,
|
||||
string $defaultController,
|
||||
string $uriByClass,
|
||||
string $classname,
|
||||
string $methodName,
|
||||
string $httpVerb,
|
||||
ReflectionMethod $method
|
||||
): array {
|
||||
$output = [];
|
||||
|
||||
if ($classShortname === $defaultController) {
|
||||
$pattern = '#' . preg_quote(lcfirst($defaultController), '#') . '\z#';
|
||||
$routeWithoutController = rtrim(preg_replace($pattern, '', $uriByClass), '/');
|
||||
$routeWithoutController = $routeWithoutController ?: '/';
|
||||
|
||||
[$params, $routeParams] = $this->getParameters($method);
|
||||
|
||||
if ($routeWithoutController === '/' && $routeParams !== '') {
|
||||
$routeWithoutController = '';
|
||||
}
|
||||
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $routeWithoutController,
|
||||
'route_params' => $routeParams,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
|
||||
/**
|
||||
* Finds all controllers in a namespace for auto route listing.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\ControllerFinderTest
|
||||
*/
|
||||
final class ControllerFinder
|
||||
{
|
||||
private readonly FileLocatorInterface $locator;
|
||||
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $namespace
|
||||
) {
|
||||
$this->locator = service('locator');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<class-string>
|
||||
*/
|
||||
public function find(): array
|
||||
{
|
||||
$nsArray = explode('\\', trim($this->namespace, '\\'));
|
||||
$count = count($nsArray);
|
||||
$ns = '';
|
||||
$files = [];
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$ns .= '\\' . array_shift($nsArray);
|
||||
$path = implode('\\', $nsArray);
|
||||
|
||||
$files = $this->locator->listNamespaceFiles($ns, $path);
|
||||
|
||||
if ($files !== []) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$classes = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (\is_file($file)) {
|
||||
$classnameOrEmpty = $this->locator->getClassname($file);
|
||||
|
||||
if ($classnameOrEmpty !== '') {
|
||||
/** @var class-string $classname */
|
||||
$classname = $classnameOrEmpty;
|
||||
|
||||
$classes[] = $classname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Reads a controller and returns a list of auto route listing.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\ControllerMethodReaderTest
|
||||
*/
|
||||
final class ControllerMethodReader
|
||||
{
|
||||
/**
|
||||
* @param string $namespace the default namespace
|
||||
*/
|
||||
public function __construct(private readonly string $namespace)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
*
|
||||
* @return list<array{route: string, handler: string}>
|
||||
*/
|
||||
public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array
|
||||
{
|
||||
$reflection = new ReflectionClass($class);
|
||||
|
||||
if ($reflection->isAbstract()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$classname = $reflection->getName();
|
||||
$classShortname = $reflection->getShortName();
|
||||
|
||||
$output = [];
|
||||
$uriByClass = $this->getUriByClass($classname);
|
||||
|
||||
if ($this->hasRemap($reflection)) {
|
||||
$methodName = '_remap';
|
||||
|
||||
$routeWithoutController = $this->getRouteWithoutController(
|
||||
$classShortname,
|
||||
$defaultController,
|
||||
$uriByClass,
|
||||
$classname,
|
||||
$methodName
|
||||
);
|
||||
$output = [...$output, ...$routeWithoutController];
|
||||
|
||||
$output[] = [
|
||||
'route' => $uriByClass . '[/...]',
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
];
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
$methodName = $method->getName();
|
||||
|
||||
$route = $uriByClass . '/' . $methodName;
|
||||
|
||||
// Exclude BaseController and initController
|
||||
// See system/Config/Routes.php
|
||||
if (preg_match('#\AbaseController.*#', $route) === 1) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('#.*/initController\z#', $route) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($methodName === $defaultMethod) {
|
||||
$routeWithoutController = $this->getRouteWithoutController(
|
||||
$classShortname,
|
||||
$defaultController,
|
||||
$uriByClass,
|
||||
$classname,
|
||||
$methodName
|
||||
);
|
||||
$output = [...$output, ...$routeWithoutController];
|
||||
|
||||
$output[] = [
|
||||
'route' => $uriByClass,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
];
|
||||
}
|
||||
|
||||
$output[] = [
|
||||
'route' => $route . '[/...]',
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the class has a _remap() method.
|
||||
*/
|
||||
private function hasRemap(ReflectionClass $class): bool
|
||||
{
|
||||
if ($class->hasMethod('_remap')) {
|
||||
$remap = $class->getMethod('_remap');
|
||||
|
||||
return $remap->isPublic();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $classname
|
||||
*
|
||||
* @return string URI path part from the folder(s) and controller
|
||||
*/
|
||||
private function getUriByClass(string $classname): string
|
||||
{
|
||||
// remove the namespace
|
||||
$pattern = '/' . preg_quote($this->namespace, '/') . '/';
|
||||
$class = ltrim(preg_replace($pattern, '', $classname), '\\');
|
||||
|
||||
$classParts = explode('\\', $class);
|
||||
$classPath = '';
|
||||
|
||||
foreach ($classParts as $part) {
|
||||
// make the first letter lowercase, because auto routing makes
|
||||
// the URI path's first letter uppercase and search the controller
|
||||
$classPath .= lcfirst($part) . '/';
|
||||
}
|
||||
|
||||
return rtrim($classPath, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a route without default controller.
|
||||
*/
|
||||
private function getRouteWithoutController(
|
||||
string $classShortname,
|
||||
string $defaultController,
|
||||
string $uriByClass,
|
||||
string $classname,
|
||||
string $methodName
|
||||
): array {
|
||||
if ($classShortname !== $defaultController) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$pattern = '#' . preg_quote(lcfirst($defaultController), '#') . '\z#';
|
||||
$routeWithoutController = rtrim(preg_replace($pattern, '', $uriByClass), '/');
|
||||
$routeWithoutController = $routeWithoutController ?: '/';
|
||||
|
||||
return [[
|
||||
'route' => $routeWithoutController,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
]];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
use CodeIgniter\Config\Services;
|
||||
use CodeIgniter\Filters\Filters;
|
||||
use CodeIgniter\HTTP\Method;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\Router\Router;
|
||||
use Config\Filters as FiltersConfig;
|
||||
|
||||
/**
|
||||
* Collects filters for a route.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\FilterCollectorTest
|
||||
*/
|
||||
final class FilterCollector
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* Whether to reset Defined Routes.
|
||||
*
|
||||
* If set to true, route filters are not found.
|
||||
*/
|
||||
private readonly bool $resetRoutes = false
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns filters for the URI
|
||||
*
|
||||
* @param string $method HTTP verb like `GET`,`POST` or `CLI`.
|
||||
* @param string $uri URI path to find filters for
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
|
||||
*/
|
||||
public function get(string $method, string $uri): array
|
||||
{
|
||||
if ($method === strtolower($method)) {
|
||||
@trigger_error(
|
||||
'Passing lowercase HTTP method "' . $method . '" is deprecated.'
|
||||
. ' Use uppercase HTTP method like "' . strtoupper($method) . '".',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.5.0
|
||||
* @TODO Remove this in the future.
|
||||
*/
|
||||
$method = strtoupper($method);
|
||||
|
||||
if ($method === 'CLI') {
|
||||
return [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$request = Services::incomingrequest(null, false);
|
||||
$request->setMethod($method);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->find($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filters
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
|
||||
*/
|
||||
public function getRequiredFilters(): array
|
||||
{
|
||||
$request = Services::incomingrequest(null, false);
|
||||
$request->setMethod(Method::GET);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->getRequiredFilters();
|
||||
}
|
||||
|
||||
private function createRouter(Request $request): Router
|
||||
{
|
||||
$routes = service('routes');
|
||||
|
||||
if ($this->resetRoutes) {
|
||||
$routes->resetRoutes();
|
||||
}
|
||||
|
||||
return new Router($routes, $request);
|
||||
}
|
||||
|
||||
private function createFilters(Request $request): Filters
|
||||
{
|
||||
$config = config(FiltersConfig::class);
|
||||
|
||||
return new Filters($config, $request, service('response'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\Filters\Filters;
|
||||
use CodeIgniter\HTTP\Exceptions\BadRequestException;
|
||||
use CodeIgniter\HTTP\Exceptions\RedirectException;
|
||||
use CodeIgniter\Router\Router;
|
||||
use Config\Feature;
|
||||
|
||||
/**
|
||||
* Finds filters.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\FilterFinderTest
|
||||
*/
|
||||
final class FilterFinder
|
||||
{
|
||||
private readonly Router $router;
|
||||
private readonly Filters $filters;
|
||||
|
||||
public function __construct(?Router $router = null, ?Filters $filters = null)
|
||||
{
|
||||
$this->router = $router ?? service('router');
|
||||
$this->filters = $filters ?? service('filters');
|
||||
}
|
||||
|
||||
private function getRouteFilters(string $uri): array
|
||||
{
|
||||
$this->router->handle($uri);
|
||||
|
||||
return $this->router->getFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri URI path to find filters for
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
|
||||
*/
|
||||
public function find(string $uri): array
|
||||
{
|
||||
$this->filters->reset();
|
||||
|
||||
// Add route filters
|
||||
try {
|
||||
$routeFilters = $this->getRouteFilters($uri);
|
||||
|
||||
$this->filters->enableFilters($routeFilters, 'before');
|
||||
|
||||
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
|
||||
if (! $oldFilterOrder) {
|
||||
$routeFilters = array_reverse($routeFilters);
|
||||
}
|
||||
|
||||
$this->filters->enableFilters($routeFilters, 'after');
|
||||
|
||||
$this->filters->initialize($uri);
|
||||
|
||||
return $this->filters->getFilters();
|
||||
} catch (RedirectException) {
|
||||
return [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
} catch (BadRequestException|PageNotFoundException) {
|
||||
return [
|
||||
'before' => ['<unknown>'],
|
||||
'after' => ['<unknown>'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filters
|
||||
*
|
||||
* @return array{before: list<string>, after:list<string>}
|
||||
*/
|
||||
public function getRequiredFilters(): array
|
||||
{
|
||||
[$requiredBefore] = $this->filters->getRequiredFilters('before');
|
||||
[$requiredAfter] = $this->filters->getRequiredFilters('after');
|
||||
|
||||
return [
|
||||
'before' => $requiredBefore,
|
||||
'after' => $requiredAfter,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Commands\Utilities\Routes;
|
||||
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
use Config\App;
|
||||
|
||||
/**
|
||||
* Generate a sample URI path from route key regex.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Utilities\Routes\SampleURIGeneratorTest
|
||||
*/
|
||||
final class SampleURIGenerator
|
||||
{
|
||||
private readonly RouteCollection $routes;
|
||||
|
||||
/**
|
||||
* Sample URI path for placeholder.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $samples = [
|
||||
'any' => '123/abc',
|
||||
'segment' => 'abc_123',
|
||||
'alphanum' => 'abc123',
|
||||
'num' => '123',
|
||||
'alpha' => 'abc',
|
||||
'hash' => 'abc_123',
|
||||
];
|
||||
|
||||
public function __construct(?RouteCollection $routes = null)
|
||||
{
|
||||
$this->routes = $routes ?? service('routes');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $routeKey route key regex
|
||||
*
|
||||
* @return string sample URI path
|
||||
*/
|
||||
public function get(string $routeKey): string
|
||||
{
|
||||
$sampleUri = $routeKey;
|
||||
|
||||
if (str_contains($routeKey, '{locale}')) {
|
||||
$sampleUri = str_replace(
|
||||
'{locale}',
|
||||
config(App::class)->defaultLocale,
|
||||
$routeKey
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->routes->getPlaceholders() as $placeholder => $regex) {
|
||||
$sample = $this->samples[$placeholder] ?? '::unknown::';
|
||||
|
||||
$sampleUri = str_replace('(' . $regex . ')', $sample, $sampleUri);
|
||||
}
|
||||
|
||||
// auto route
|
||||
return str_replace('[/...]', '/1/2/3/4/5', $sampleUri);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user