first commit
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$config = new Autoload();
|
||||
|
||||
$tbody = [];
|
||||
|
||||
foreach ($config->psr4 as $ns => $path) {
|
||||
$path = realpath($path) ?: $path;
|
||||
|
||||
$tbody[] = [
|
||||
$ns,
|
||||
realpath($path) ?: $path,
|
||||
is_dir($path) ? 'Yes' : 'MISSING',
|
||||
];
|
||||
}
|
||||
|
||||
$thead = [
|
||||
'Namespace',
|
||||
'Path',
|
||||
'Found?',
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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', [
|
||||
get_class($publisher),
|
||||
count($publisher->getPublished()),
|
||||
$publisher->getDestination(),
|
||||
]), 'green');
|
||||
} else {
|
||||
CLI::error(lang('Publisher.publishFailure', [
|
||||
get_class($publisher),
|
||||
$publisher->getDestination(),
|
||||
]), 'light_gray', 'red');
|
||||
|
||||
foreach ($publisher->getErrors() as $file => $exception) {
|
||||
CLI::write($file);
|
||||
CLI::error($exception->getMessage());
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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 Closure;
|
||||
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 Config\Services;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$collection = Services::routes(true);
|
||||
$methods = [
|
||||
'get',
|
||||
'head',
|
||||
'post',
|
||||
'patch',
|
||||
'put',
|
||||
'delete',
|
||||
'options',
|
||||
'trace',
|
||||
'connect',
|
||||
'cli',
|
||||
];
|
||||
|
||||
$tbody = [];
|
||||
$uriGenerator = new SampleURIGenerator();
|
||||
$filterCollector = new FilterCollector();
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$routes = $collection->getRoutes($method);
|
||||
|
||||
foreach ($routes as $route => $handler) {
|
||||
if (is_string($handler) || $handler instanceof Closure) {
|
||||
$sampleUri = $uriGenerator->get($route);
|
||||
$filters = $filterCollector->get($method, $sampleUri);
|
||||
|
||||
$tbody[] = [
|
||||
strtoupper($method),
|
||||
$route,
|
||||
is_string($handler) ? $handler : '(Closure)',
|
||||
implode(' ', array_map('class_basename', $filters['before'])),
|
||||
implode(' ', array_map('class_basename', $filters['after'])),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($collection->shouldAutoRoute()) {
|
||||
$autoRoutesImproved = config('Feature')->autoRoutesImproved ?? false;
|
||||
|
||||
if ($autoRoutesImproved) {
|
||||
$autoRouteCollector = new AutoRouteCollectorImproved(
|
||||
$collection->getDefaultNamespace(),
|
||||
$collection->getDefaultController(),
|
||||
$collection->getDefaultMethod(),
|
||||
$methods,
|
||||
$collection->getRegisteredControllers('*')
|
||||
);
|
||||
|
||||
$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',
|
||||
'Handler',
|
||||
'Before Filters',
|
||||
'After Filters',
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
final class AutoRouteCollector
|
||||
{
|
||||
/**
|
||||
* @var string namespace to search
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
private string $defaultController;
|
||||
private string $defaultMethod;
|
||||
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
*/
|
||||
public function __construct(string $namespace, string $defaultController, string $defaultMethod)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->defaultController = $defaultController;
|
||||
$this->defaultMethod = $defaultMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, string>>
|
||||
* @phpstan-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,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
final class AutoRouteCollector
|
||||
{
|
||||
/**
|
||||
* @var string namespace to search
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
private string $defaultController;
|
||||
private string $defaultMethod;
|
||||
private array $httpMethods;
|
||||
|
||||
/**
|
||||
* List of controllers in Defined Routes that should not be accessed via Auto-Routing.
|
||||
*
|
||||
* @var class-string[]
|
||||
*/
|
||||
private array $protectedControllers;
|
||||
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
*/
|
||||
public function __construct(
|
||||
string $namespace,
|
||||
string $defaultController,
|
||||
string $defaultMethod,
|
||||
array $httpMethods,
|
||||
array $protectedControllers
|
||||
) {
|
||||
$this->namespace = $namespace;
|
||||
$this->defaultController = $defaultController;
|
||||
$this->defaultMethod = $defaultMethod;
|
||||
$this->httpMethods = $httpMethods;
|
||||
$this->protectedControllers = $protectedControllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, string>>
|
||||
* @phpstan-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) {
|
||||
$tbody[] = [
|
||||
strtoupper($item['method']) . '(auto)',
|
||||
$item['route'] . $item['route_params'],
|
||||
$item['handler'],
|
||||
$item['before'],
|
||||
$item['after'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tbody;
|
||||
}
|
||||
|
||||
private function addFilters($routes)
|
||||
{
|
||||
$filterCollector = new FilterCollector(true);
|
||||
|
||||
foreach ($routes as &$route) {
|
||||
// Search filters for the URI with all params
|
||||
$sampleUri = $this->generateSampleUri($route);
|
||||
$filtersLongest = $filterCollector->get($route['method'], $route['route'] . $sampleUri);
|
||||
|
||||
// Search filters for the URI without optional params
|
||||
$sampleUri = $this->generateSampleUri($route, false);
|
||||
$filtersShortest = $filterCollector->get($route['method'], $route['route'] . $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,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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 ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Reads a controller and returns a list of auto route listing.
|
||||
*/
|
||||
final class ControllerMethodReader
|
||||
{
|
||||
/**
|
||||
* @var string the default namespace
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
private array $httpMethods;
|
||||
|
||||
/**
|
||||
* @param string $namespace the default namespace
|
||||
*/
|
||||
public function __construct(string $namespace, array $httpMethods)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->httpMethods = $httpMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns found route info in the controller.
|
||||
*
|
||||
* @phpstan-param class-string $class
|
||||
*
|
||||
* @return array<int, array<string, array|string>>
|
||||
* @phpstan-return list<array<string, string|array>>
|
||||
*/
|
||||
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->getUriByClass($classname);
|
||||
|
||||
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
$methodName = $method->getName();
|
||||
|
||||
foreach ($this->httpMethods as $httpVerb) {
|
||||
if (strpos($methodName, $httpVerb) === 0) {
|
||||
// Remove HTTP verb prefix.
|
||||
$methodInUri = lcfirst(substr($methodName, strlen($httpVerb)));
|
||||
|
||||
if ($methodInUri === $defaultMethod) {
|
||||
$routeWithoutController = $this->getRouteWithoutController(
|
||||
$classShortname,
|
||||
$defaultController,
|
||||
$classInUri,
|
||||
$classname,
|
||||
$methodName,
|
||||
$httpVerb
|
||||
);
|
||||
|
||||
if ($routeWithoutController !== []) {
|
||||
$output = [...$output, ...$routeWithoutController];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Route for the default method.
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $classInUri,
|
||||
'route_params' => '',
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => [],
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$route = $classInUri . '/' . $methodInUri;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $route,
|
||||
'route_params' => $routeParams,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-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,
|
||||
string $httpVerb
|
||||
): array {
|
||||
$output = [];
|
||||
|
||||
if ($classShortname === $defaultController) {
|
||||
$pattern = '#' . preg_quote(lcfirst($defaultController), '#') . '\z#';
|
||||
$routeWithoutController = rtrim(preg_replace($pattern, '', $uriByClass), '/');
|
||||
$routeWithoutController = $routeWithoutController ?: '/';
|
||||
|
||||
$output[] = [
|
||||
'method' => $httpVerb,
|
||||
'route' => $routeWithoutController,
|
||||
'route_params' => '',
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
'params' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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\FileLocator;
|
||||
use CodeIgniter\Config\Services;
|
||||
|
||||
/**
|
||||
* Finds all controllers in a namespace for auto route listing.
|
||||
*/
|
||||
final class ControllerFinder
|
||||
{
|
||||
/**
|
||||
* @var string namespace to search
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
private FileLocator $locator;
|
||||
|
||||
/**
|
||||
* @param string $namespace namespace to search
|
||||
*/
|
||||
public function __construct(string $namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->locator = Services::locator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return class-string[]
|
||||
*/
|
||||
public function find(): array
|
||||
{
|
||||
$nsArray = explode('\\', trim($this->namespace, '\\'));
|
||||
$count = count($nsArray);
|
||||
$ns = '';
|
||||
|
||||
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 !== '') {
|
||||
/** @phpstan-var class-string $classname */
|
||||
$classname = $classnameOrEmpty;
|
||||
|
||||
$classes[] = $classname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
final class ControllerMethodReader
|
||||
{
|
||||
/**
|
||||
* @var string the default namespace
|
||||
*/
|
||||
private string $namespace;
|
||||
|
||||
/**
|
||||
* @param string $namespace the default namespace
|
||||
*/
|
||||
public function __construct(string $namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param class-string $class
|
||||
*
|
||||
* @return array<int, array{route: string, handler: string}>
|
||||
* @phpstan-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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-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 {
|
||||
$output = [];
|
||||
|
||||
if ($classShortname === $defaultController) {
|
||||
$pattern = '#' . preg_quote(lcfirst($defaultController), '#') . '\z#';
|
||||
$routeWithoutController = rtrim(preg_replace($pattern, '', $uriByClass), '/');
|
||||
$routeWithoutController = $routeWithoutController ?: '/';
|
||||
|
||||
$output[] = [
|
||||
'route' => $routeWithoutController,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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\Request;
|
||||
use CodeIgniter\Router\Router;
|
||||
|
||||
/**
|
||||
* Collects filters for a route.
|
||||
*/
|
||||
final class FilterCollector
|
||||
{
|
||||
/**
|
||||
* Whether to reset Defined Routes.
|
||||
*
|
||||
* If set to true, route filters are not found.
|
||||
*/
|
||||
private bool $resetRoutes;
|
||||
|
||||
public function __construct(bool $resetRoutes = false)
|
||||
{
|
||||
$this->resetRoutes = $resetRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method HTTP method
|
||||
* @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 === 'cli') {
|
||||
return [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$request = Services::request(null, false);
|
||||
$request->setMethod($method);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->find($uri);
|
||||
}
|
||||
|
||||
private function createRouter(Request $request): Router
|
||||
{
|
||||
$routes = Services::routes();
|
||||
|
||||
if ($this->resetRoutes) {
|
||||
$routes->resetRoutes();
|
||||
}
|
||||
|
||||
return new Router($routes, $request);
|
||||
}
|
||||
|
||||
private function createFilters(Request $request): Filters
|
||||
{
|
||||
$config = config('Filters');
|
||||
|
||||
return new Filters($config, $request, Services::response());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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\Router\Exceptions\RedirectException;
|
||||
use CodeIgniter\Router\Router;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
* Finds filters.
|
||||
*/
|
||||
final class FilterFinder
|
||||
{
|
||||
private Router $router;
|
||||
private Filters $filters;
|
||||
|
||||
public function __construct(?Router $router = null, ?Filters $filters = null)
|
||||
{
|
||||
$this->router = $router ?? Services::router();
|
||||
$this->filters = $filters ?? Services::filters();
|
||||
}
|
||||
|
||||
private function getRouteFilters(string $uri): array
|
||||
{
|
||||
$this->router->handle($uri);
|
||||
|
||||
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
||||
if (! $multipleFiltersEnabled) {
|
||||
$filter = $this->router->getFilter();
|
||||
|
||||
return $filter === null ? [] : [$filter];
|
||||
}
|
||||
|
||||
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');
|
||||
$this->filters->enableFilters($routeFilters, 'after');
|
||||
|
||||
$this->filters->initialize($uri);
|
||||
|
||||
return $this->filters->getFilters();
|
||||
} catch (RedirectException $e) {
|
||||
return [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
} catch (PageNotFoundException $e) {
|
||||
return [
|
||||
'before' => ['<unknown>'],
|
||||
'after' => ['<unknown>'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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\Router\RouteCollection;
|
||||
|
||||
/**
|
||||
* Generate a sample URI path from route key regex.
|
||||
*/
|
||||
final class SampleURIGenerator
|
||||
{
|
||||
private 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 ?? Services::routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $routeKey route key regex
|
||||
*
|
||||
* @return string sample URI path
|
||||
*/
|
||||
public function get(string $routeKey): string
|
||||
{
|
||||
$sampleUri = $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