add trashes
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !authz_core_module>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
@@ -0,0 +1,362 @@
|
||||
<?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\API;
|
||||
|
||||
use CodeIgniter\Format\FormatterInterface;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Provides common, more readable, methods to provide
|
||||
* consistent HTTP responses under a variety of common
|
||||
* situations when working as an API.
|
||||
*
|
||||
* @property bool $stringAsHtml Whether to treat string data as HTML in JSON response.
|
||||
* Setting `true` is only for backward compatibility.
|
||||
*/
|
||||
trait ResponseTrait
|
||||
{
|
||||
/**
|
||||
* Allows child classes to override the
|
||||
* status code that is used in their API.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
protected $codes = [
|
||||
'created' => 201,
|
||||
'deleted' => 200,
|
||||
'updated' => 200,
|
||||
'no_content' => 204,
|
||||
'invalid_request' => 400,
|
||||
'unsupported_response_type' => 400,
|
||||
'invalid_scope' => 400,
|
||||
'temporarily_unavailable' => 400,
|
||||
'invalid_grant' => 400,
|
||||
'invalid_credentials' => 400,
|
||||
'invalid_refresh' => 400,
|
||||
'no_data' => 400,
|
||||
'invalid_data' => 400,
|
||||
'access_denied' => 401,
|
||||
'unauthorized' => 401,
|
||||
'invalid_client' => 401,
|
||||
'forbidden' => 403,
|
||||
'resource_not_found' => 404,
|
||||
'not_acceptable' => 406,
|
||||
'resource_exists' => 409,
|
||||
'conflict' => 409,
|
||||
'resource_gone' => 410,
|
||||
'payload_too_large' => 413,
|
||||
'unsupported_media_type' => 415,
|
||||
'too_many_requests' => 429,
|
||||
'server_error' => 500,
|
||||
'unsupported_grant_type' => 501,
|
||||
'not_implemented' => 501,
|
||||
];
|
||||
|
||||
/**
|
||||
* How to format the response data.
|
||||
* Either 'json' or 'xml'. If null is set, it will be determined through
|
||||
* content negotiation.
|
||||
*
|
||||
* @var string|null
|
||||
* @phpstan-var 'html'|'json'|'xml'|null
|
||||
*/
|
||||
protected $format = 'json';
|
||||
|
||||
/**
|
||||
* Current Formatter instance. This is usually set by ResponseTrait::format
|
||||
*
|
||||
* @var FormatterInterface|null
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
* Provides a single, simple method to return an API response, formatted
|
||||
* to match the requested format, with proper content-type and status code.
|
||||
*
|
||||
* @param array|string|null $data
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function respond($data = null, ?int $status = null, string $message = '')
|
||||
{
|
||||
if ($data === null && $status === null) {
|
||||
$status = 404;
|
||||
$output = null;
|
||||
$this->format($data);
|
||||
} elseif ($data === null && is_numeric($status)) {
|
||||
$output = null;
|
||||
$this->format($data);
|
||||
} else {
|
||||
$status ??= 200;
|
||||
$output = $this->format($data);
|
||||
}
|
||||
|
||||
if ($output !== null) {
|
||||
if ($this->format === 'json') {
|
||||
return $this->response->setJSON($output)->setStatusCode($status, $message);
|
||||
}
|
||||
|
||||
if ($this->format === 'xml') {
|
||||
return $this->response->setXML($output)->setStatusCode($status, $message);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->setBody($output)->setStatusCode($status, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for generic failures that no custom methods exist for.
|
||||
*
|
||||
* @param array|string $messages
|
||||
* @param int $status HTTP status code
|
||||
* @param string|null $code Custom, API-specific, error code
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function fail($messages, int $status = 400, ?string $code = null, string $customMessage = '')
|
||||
{
|
||||
if (! is_array($messages)) {
|
||||
$messages = ['error' => $messages];
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => $status,
|
||||
'error' => $code ?? $status,
|
||||
'messages' => $messages,
|
||||
];
|
||||
|
||||
return $this->respond($response, $status, $customMessage);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Response Helpers
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Used after successfully creating a new resource.
|
||||
*
|
||||
* @param array|string|null $data
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function respondCreated($data = null, string $message = '')
|
||||
{
|
||||
return $this->respond($data, $this->codes['created'], $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used after a resource has been successfully deleted.
|
||||
*
|
||||
* @param array|string|null $data
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function respondDeleted($data = null, string $message = '')
|
||||
{
|
||||
return $this->respond($data, $this->codes['deleted'], $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used after a resource has been successfully updated.
|
||||
*
|
||||
* @param array|string|null $data
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function respondUpdated($data = null, string $message = '')
|
||||
{
|
||||
return $this->respond($data, $this->codes['updated'], $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used after a command has been successfully executed but there is no
|
||||
* meaningful reply to send back to the client.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function respondNoContent(string $message = 'No Content')
|
||||
{
|
||||
return $this->respond(null, $this->codes['no_content'], $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when the client is either didn't send authorization information,
|
||||
* or had bad authorization credentials. User is encouraged to try again
|
||||
* with the proper information.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failUnauthorized(string $description = 'Unauthorized', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['unauthorized'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when access is always denied to this resource and no amount
|
||||
* of trying again will help.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failForbidden(string $description = 'Forbidden', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['forbidden'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a specified resource cannot be found.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failNotFound(string $description = 'Not Found', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['resource_not_found'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when the data provided by the client cannot be validated on one or more fields.
|
||||
*
|
||||
* @param list<string>|string $errors
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failValidationErrors($errors, ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($errors, $this->codes['invalid_data'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when trying to create a new resource and it already exists.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failResourceExists(string $description = 'Conflict', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['resource_exists'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when a resource was previously deleted. This is different than
|
||||
* Not Found, because here we know the data previously existed, but is now gone,
|
||||
* where Not Found means we simply cannot find any information about it.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failResourceGone(string $description = 'Gone', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['resource_gone'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when the user has made too many requests for the resource recently.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function failTooManyRequests(string $description = 'Too Many Requests', ?string $code = null, string $message = '')
|
||||
{
|
||||
return $this->fail($description, $this->codes['too_many_requests'], $code, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when there is a server error.
|
||||
*
|
||||
* @param string $description The error message to show the user.
|
||||
* @param string|null $code A custom, API-specific, error code.
|
||||
* @param string $message A custom "reason" message to return.
|
||||
*/
|
||||
protected function failServerError(string $description = 'Internal Server Error', ?string $code = null, string $message = ''): ResponseInterface
|
||||
{
|
||||
return $this->fail($description, $this->codes['server_error'], $code, $message);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Utility Methods
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handles formatting a response. Currently, makes some heavy assumptions
|
||||
* and needs updating! :)
|
||||
*
|
||||
* @param array|string|null $data
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function format($data = null)
|
||||
{
|
||||
$format = service('format');
|
||||
|
||||
$mime = ($this->format === null) ? $format->getConfig()->supportedResponseFormats[0]
|
||||
: "application/{$this->format}";
|
||||
|
||||
// Determine correct response type through content negotiation if not explicitly declared
|
||||
if (
|
||||
! in_array($this->format, ['json', 'xml'], true)
|
||||
&& $this->request instanceof IncomingRequest
|
||||
) {
|
||||
$mime = $this->request->negotiate(
|
||||
'media',
|
||||
$format->getConfig()->supportedResponseFormats,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->setContentType($mime);
|
||||
|
||||
// if we don't have a formatter, make one
|
||||
if (! isset($this->formatter)) {
|
||||
// if no formatter, use the default
|
||||
$this->formatter = $format->getFormatter($mime);
|
||||
}
|
||||
|
||||
$asHtml = $this->stringAsHtml ?? false;
|
||||
|
||||
// Returns as HTML.
|
||||
if (
|
||||
($mime === 'application/json' && $asHtml && is_string($data))
|
||||
|| ($mime !== 'application/json' && is_string($data))
|
||||
) {
|
||||
// The content type should be text/... and not application/...
|
||||
$contentType = $this->response->getHeaderLine('Content-Type');
|
||||
$contentType = str_replace('application/json', 'text/html', $contentType);
|
||||
$contentType = str_replace('application/', 'text/', $contentType);
|
||||
$this->response->setContentType($contentType);
|
||||
$this->format = 'html';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ($mime !== 'application/json') {
|
||||
// Recursively convert objects into associative arrays
|
||||
// Conversion not required for JSONFormatter
|
||||
$data = json_decode(json_encode($data), true);
|
||||
}
|
||||
|
||||
return $this->formatter->format($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the format the response should be in.
|
||||
*
|
||||
* @param string|null $format Response format
|
||||
* @phpstan-param 'json'|'xml' $format
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setResponseFormat(?string $format = null)
|
||||
{
|
||||
$this->format = ($format === null) ? null : strtolower($format);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
<?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\Autoloader;
|
||||
|
||||
use CodeIgniter\Exceptions\ConfigException;
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\InstalledVersions;
|
||||
use Config\Autoload;
|
||||
use Config\Kint as KintConfig;
|
||||
use Config\Modules;
|
||||
use Kint;
|
||||
use Kint\Renderer\CliRenderer;
|
||||
use Kint\Renderer\RichRenderer;
|
||||
|
||||
/**
|
||||
* An autoloader that uses both PSR4 autoloading, and traditional classmaps.
|
||||
*
|
||||
* Given a foo-bar package of classes in the file system at the following paths:
|
||||
* ```
|
||||
* /path/to/packages/foo-bar/
|
||||
* /src
|
||||
* Baz.php # Foo\Bar\Baz
|
||||
* Qux/
|
||||
* Quux.php # Foo\Bar\Qux\Quux
|
||||
* ```
|
||||
* you can add the path to the configuration array that is passed in the constructor.
|
||||
* The Config array consists of 2 primary keys, both of which are associative arrays:
|
||||
* 'psr4', and 'classmap'.
|
||||
* ```
|
||||
* $Config = [
|
||||
* 'psr4' => [
|
||||
* 'Foo\Bar' => '/path/to/packages/foo-bar'
|
||||
* ],
|
||||
* 'classmap' => [
|
||||
* 'MyClass' => '/path/to/class/file.php'
|
||||
* ]
|
||||
* ];
|
||||
* ```
|
||||
* Example:
|
||||
* ```
|
||||
* <?php
|
||||
* // our configuration array
|
||||
* $Config = [ ... ];
|
||||
* $loader = new \CodeIgniter\Autoloader\Autoloader($Config);
|
||||
*
|
||||
* // register the autoloader
|
||||
* $loader->register();
|
||||
* ```
|
||||
*
|
||||
* @see \CodeIgniter\Autoloader\AutoloaderTest
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/**
|
||||
* Stores namespaces as key, and path as values.
|
||||
*
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
protected $prefixes = [];
|
||||
|
||||
/**
|
||||
* Stores class name as key, and path as values.
|
||||
*
|
||||
* @var array<class-string, string>
|
||||
*/
|
||||
protected $classmap = [];
|
||||
|
||||
/**
|
||||
* Stores files as a list.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $files = [];
|
||||
|
||||
/**
|
||||
* Stores helper list.
|
||||
* Always load the URL helper, it should be used in most apps.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = ['url'];
|
||||
|
||||
/**
|
||||
* Reads in the configuration array (described above) and stores
|
||||
* the valid parts that we'll need.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function initialize(Autoload $config, Modules $modules)
|
||||
{
|
||||
$this->prefixes = [];
|
||||
$this->classmap = [];
|
||||
$this->files = [];
|
||||
|
||||
// We have to have one or the other, though we don't enforce the need
|
||||
// to have both present in order to work.
|
||||
if ($config->psr4 === [] && $config->classmap === []) {
|
||||
throw new InvalidArgumentException('Config array must contain either the \'psr4\' key or the \'classmap\' key.');
|
||||
}
|
||||
|
||||
if ($config->psr4 !== []) {
|
||||
$this->addNamespace($config->psr4);
|
||||
}
|
||||
|
||||
if ($config->classmap !== []) {
|
||||
$this->classmap = $config->classmap;
|
||||
}
|
||||
|
||||
if ($config->files !== []) {
|
||||
$this->files = $config->files;
|
||||
}
|
||||
|
||||
if ($config->helpers !== []) {
|
||||
$this->helpers = [...$this->helpers, ...$config->helpers];
|
||||
}
|
||||
|
||||
if (is_file(COMPOSER_PATH)) {
|
||||
$this->loadComposerAutoloader($modules);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function loadComposerAutoloader(Modules $modules): void
|
||||
{
|
||||
// The path to the vendor directory.
|
||||
// We do not want to enforce this, so set the constant if Composer was used.
|
||||
if (! defined('VENDORPATH')) {
|
||||
define('VENDORPATH', dirname(COMPOSER_PATH) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
/** @var ClassLoader $composer */
|
||||
$composer = include COMPOSER_PATH;
|
||||
|
||||
// Should we load through Composer's namespaces, also?
|
||||
if ($modules->discoverInComposer) {
|
||||
// @phpstan-ignore-next-line
|
||||
$this->loadComposerNamespaces($composer, $modules->composerPackages ?? []);
|
||||
}
|
||||
|
||||
unset($composer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the loader with the SPL autoloader stack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// Register classmap loader for the files in our class map.
|
||||
spl_autoload_register($this->loadClassmap(...), true);
|
||||
|
||||
// Register the PSR-4 autoloader.
|
||||
spl_autoload_register($this->loadClass(...), true);
|
||||
|
||||
// Load our non-class files
|
||||
foreach ($this->files as $file) {
|
||||
$this->includeFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister autoloader.
|
||||
*
|
||||
* This method is for testing.
|
||||
*/
|
||||
public function unregister(): void
|
||||
{
|
||||
spl_autoload_unregister($this->loadClass(...));
|
||||
spl_autoload_unregister($this->loadClassmap(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers namespaces with the autoloader.
|
||||
*
|
||||
* @param array<string, list<string>|string>|string $namespace
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addNamespace($namespace, ?string $path = null)
|
||||
{
|
||||
if (is_array($namespace)) {
|
||||
foreach ($namespace as $prefix => $namespacedPath) {
|
||||
$prefix = trim($prefix, '\\');
|
||||
|
||||
if (is_array($namespacedPath)) {
|
||||
foreach ($namespacedPath as $dir) {
|
||||
$this->prefixes[$prefix][] = rtrim($dir, '\\/') . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->prefixes[$prefix][] = rtrim($namespacedPath, '\\/') . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
} else {
|
||||
$this->prefixes[trim($namespace, '\\')][] = rtrim($path, '\\/') . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get namespaces with prefixes as keys and paths as values.
|
||||
*
|
||||
* If a prefix param is set, returns only paths to the given prefix.
|
||||
*
|
||||
* @return array<string, list<string>>|list<string>
|
||||
* @phpstan-return ($prefix is null ? array<string, list<string>> : list<string>)
|
||||
*/
|
||||
public function getNamespace(?string $prefix = null)
|
||||
{
|
||||
if ($prefix === null) {
|
||||
return $this->prefixes;
|
||||
}
|
||||
|
||||
return $this->prefixes[trim($prefix, '\\')] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single namespace from the psr4 settings.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeNamespace(string $namespace)
|
||||
{
|
||||
if (isset($this->prefixes[trim($namespace, '\\')])) {
|
||||
unset($this->prefixes[trim($namespace, '\\')]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class using available class mapping.
|
||||
*
|
||||
* @internal For `spl_autoload_register` use.
|
||||
*/
|
||||
public function loadClassmap(string $class): void
|
||||
{
|
||||
$file = $this->classmap[$class] ?? '';
|
||||
|
||||
if (is_string($file) && $file !== '') {
|
||||
$this->includeFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the class file for a given class name.
|
||||
*
|
||||
* @internal For `spl_autoload_register` use.
|
||||
*
|
||||
* @param string $class The fully qualified class name.
|
||||
*/
|
||||
public function loadClass(string $class): void
|
||||
{
|
||||
$this->loadInNamespace($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the class file for a given class name.
|
||||
*
|
||||
* @param string $class The fully-qualified class name
|
||||
*
|
||||
* @return false|string The mapped file name on success, or boolean false on fail
|
||||
*/
|
||||
protected function loadInNamespace(string $class)
|
||||
{
|
||||
if (! str_contains($class, '\\')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->prefixes as $namespace => $directories) {
|
||||
if (str_starts_with($class, $namespace)) {
|
||||
$relativeClassPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen($namespace)));
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$directory = rtrim($directory, '\\/');
|
||||
|
||||
$filePath = $directory . $relativeClassPath . '.php';
|
||||
$filename = $this->includeFile($filePath);
|
||||
|
||||
if ($filename) {
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// never found a mapped file
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A central way to include a file. Split out primarily for testing purposes.
|
||||
*
|
||||
* @return false|string The filename on success, false if the file is not loaded
|
||||
*/
|
||||
protected function includeFile(string $file)
|
||||
{
|
||||
if (is_file($file)) {
|
||||
include_once $file;
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file path.
|
||||
*
|
||||
* Checks special characters that are illegal in filenames on certain
|
||||
* operating systems and special characters requiring special escaping
|
||||
* to manipulate at the command line. Replaces spaces and consecutive
|
||||
* dashes with a single dash. Trim period, dash and underscore from beginning
|
||||
* and end of filename.
|
||||
*
|
||||
* @return string The sanitized filename
|
||||
*
|
||||
* @deprecated No longer used. See https://github.com/codeigniter4/CodeIgniter4/issues/7055
|
||||
*/
|
||||
public function sanitizeFilename(string $filename): string
|
||||
{
|
||||
// Only allow characters deemed safe for POSIX portable filenames.
|
||||
// Plus the forward slash for directory separators since this might be a path.
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_278
|
||||
// Modified to allow backslash and colons for on Windows machines.
|
||||
$result = preg_match_all('/[^0-9\p{L}\s\/\-_.:\\\\]/u', $filename, $matches);
|
||||
|
||||
if ($result > 0) {
|
||||
$chars = implode('', $matches[0]);
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
'The file path contains special characters "' . $chars
|
||||
. '" that are not allowed: "' . $filename . '"',
|
||||
);
|
||||
}
|
||||
if ($result === false) {
|
||||
$message = preg_last_error_msg();
|
||||
|
||||
throw new RuntimeException($message . '. filename: "' . $filename . '"');
|
||||
}
|
||||
|
||||
// Clean up our filename edges.
|
||||
$cleanFilename = trim($filename, '.-_');
|
||||
|
||||
if ($filename !== $cleanFilename) {
|
||||
throw new InvalidArgumentException('The characters ".-_" are not allowed in filename edges: "' . $filename . '"');
|
||||
}
|
||||
|
||||
return $cleanFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{only?: list<string>, exclude?: list<string>} $composerPackages
|
||||
*/
|
||||
private function loadComposerNamespaces(ClassLoader $composer, array $composerPackages): void
|
||||
{
|
||||
$namespacePaths = $composer->getPrefixesPsr4();
|
||||
|
||||
// Get rid of duplicated namespaces.
|
||||
$duplicatedNamespaces = ['CodeIgniter', APP_NAMESPACE, 'Config'];
|
||||
|
||||
foreach ($duplicatedNamespaces as $ns) {
|
||||
if (isset($namespacePaths[$ns . '\\'])) {
|
||||
unset($namespacePaths[$ns . '\\']);
|
||||
}
|
||||
}
|
||||
|
||||
if (! method_exists(InstalledVersions::class, 'getAllRawData')) { // @phpstan-ignore function.alreadyNarrowedType
|
||||
throw new RuntimeException(
|
||||
'Your Composer version is too old.'
|
||||
. ' Please update Composer (run `composer self-update`) to v2.0.14 or later'
|
||||
. ' and remove your vendor/ directory, and run `composer update`.',
|
||||
);
|
||||
}
|
||||
// This method requires Composer 2.0.14 or later.
|
||||
$allData = InstalledVersions::getAllRawData();
|
||||
$packageList = [];
|
||||
|
||||
foreach ($allData as $list) {
|
||||
$packageList = array_merge($packageList, $list['versions']);
|
||||
}
|
||||
|
||||
// Check config for $composerPackages.
|
||||
$only = $composerPackages['only'] ?? [];
|
||||
$exclude = $composerPackages['exclude'] ?? [];
|
||||
if ($only !== [] && $exclude !== []) {
|
||||
throw new ConfigException('Cannot use "only" and "exclude" at the same time in "Config\Modules::$composerPackages".');
|
||||
}
|
||||
|
||||
// Get install paths of packages to add namespace for auto-discovery.
|
||||
$installPaths = [];
|
||||
if ($only !== []) {
|
||||
foreach ($packageList as $packageName => $data) {
|
||||
if (in_array($packageName, $only, true) && isset($data['install_path'])) {
|
||||
$installPaths[] = $data['install_path'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($packageList as $packageName => $data) {
|
||||
if (! in_array($packageName, $exclude, true) && isset($data['install_path'])) {
|
||||
$installPaths[] = $data['install_path'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$newPaths = [];
|
||||
|
||||
foreach ($namespacePaths as $namespace => $srcPaths) {
|
||||
$add = false;
|
||||
|
||||
foreach ($srcPaths as $path) {
|
||||
foreach ($installPaths as $installPath) {
|
||||
if (str_starts_with($path, $installPath)) {
|
||||
$add = true;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($add) {
|
||||
// Composer stores namespaces with trailing slash. We don't.
|
||||
$newPaths[rtrim($namespace, '\\ ')] = $srcPaths;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addNamespace($newPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates autoload information from Composer, if available.
|
||||
*
|
||||
* @deprecated No longer used.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function discoverComposerNamespaces()
|
||||
{
|
||||
if (! is_file(COMPOSER_PATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ClassLoader $composer
|
||||
*/
|
||||
$composer = include COMPOSER_PATH;
|
||||
$paths = $composer->getPrefixesPsr4();
|
||||
$classes = $composer->getClassMap();
|
||||
|
||||
unset($composer);
|
||||
|
||||
// Get rid of CodeIgniter so we don't have duplicates
|
||||
if (isset($paths['CodeIgniter\\'])) {
|
||||
unset($paths['CodeIgniter\\']);
|
||||
}
|
||||
|
||||
$newPaths = [];
|
||||
|
||||
foreach ($paths as $key => $value) {
|
||||
// Composer stores namespaces with trailing slash. We don't.
|
||||
$newPaths[rtrim($key, '\\ ')] = $value;
|
||||
}
|
||||
|
||||
$this->prefixes = array_merge($this->prefixes, $newPaths);
|
||||
$this->classmap = array_merge($this->classmap, $classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads helpers
|
||||
*/
|
||||
public function loadHelpers(): void
|
||||
{
|
||||
helper($this->helpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Kint
|
||||
*/
|
||||
public function initializeKint(bool $debug = false): void
|
||||
{
|
||||
if ($debug) {
|
||||
$this->autoloadKint();
|
||||
$this->configureKint();
|
||||
} elseif (class_exists(Kint::class)) {
|
||||
// In case that Kint is already loaded via Composer.
|
||||
Kint::$enabled_mode = false;
|
||||
}
|
||||
|
||||
helper('kint');
|
||||
}
|
||||
|
||||
private function autoloadKint(): void
|
||||
{
|
||||
// If we have KINT_DIR it means it's already loaded via composer
|
||||
if (! defined('KINT_DIR')) {
|
||||
spl_autoload_register(function ($class): void {
|
||||
$class = explode('\\', $class);
|
||||
|
||||
if (array_shift($class) !== 'Kint') {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = SYSTEMPATH . 'ThirdParty/Kint/' . implode('/', $class) . '.php';
|
||||
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
}
|
||||
});
|
||||
|
||||
require_once SYSTEMPATH . 'ThirdParty/Kint/init.php';
|
||||
}
|
||||
}
|
||||
|
||||
private function configureKint(): void
|
||||
{
|
||||
$config = new KintConfig();
|
||||
|
||||
Kint::$depth_limit = $config->maxDepth;
|
||||
Kint::$display_called_from = $config->displayCalledFrom;
|
||||
Kint::$expanded = $config->expanded;
|
||||
|
||||
if (isset($config->plugins) && is_array($config->plugins)) {
|
||||
Kint::$plugins = $config->plugins;
|
||||
}
|
||||
|
||||
$csp = service('csp');
|
||||
if ($csp->enabled()) {
|
||||
RichRenderer::$js_nonce = $csp->getScriptNonce();
|
||||
RichRenderer::$css_nonce = $csp->getStyleNonce();
|
||||
}
|
||||
|
||||
RichRenderer::$theme = $config->richTheme;
|
||||
RichRenderer::$folder = $config->richFolder;
|
||||
|
||||
if (isset($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
|
||||
RichRenderer::$value_plugins = $config->richObjectPlugins;
|
||||
}
|
||||
if (isset($config->richTabPlugins) && is_array($config->richTabPlugins)) {
|
||||
RichRenderer::$tab_plugins = $config->richTabPlugins;
|
||||
}
|
||||
|
||||
CliRenderer::$cli_colors = $config->cliColors;
|
||||
CliRenderer::$force_utf8 = $config->cliForceUTF8;
|
||||
CliRenderer::$detect_width = $config->cliDetectWidth;
|
||||
CliRenderer::$min_terminal_width = $config->cliMinWidth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
<?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\Autoloader;
|
||||
|
||||
/**
|
||||
* Allows loading non-class files in a namespaced manner.
|
||||
* Works with Helpers, Views, etc.
|
||||
*
|
||||
* @see \CodeIgniter\Autoloader\FileLocatorTest
|
||||
*/
|
||||
class FileLocator implements FileLocatorInterface
|
||||
{
|
||||
/**
|
||||
* The Autoloader to use.
|
||||
*
|
||||
* @var Autoloader
|
||||
*/
|
||||
protected $autoloader;
|
||||
|
||||
/**
|
||||
* List of classnames that did not exist.
|
||||
*
|
||||
* @var list<class-string>
|
||||
*/
|
||||
private array $invalidClassnames = [];
|
||||
|
||||
public function __construct(Autoloader $autoloader)
|
||||
{
|
||||
$this->autoloader = $autoloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to locate a file by examining the name for a namespace
|
||||
* and looking through the PSR-4 namespaced files that we know about.
|
||||
*
|
||||
* @param string $file The relative file path or namespaced file to
|
||||
* locate. If not namespaced, search in the app
|
||||
* folder.
|
||||
* @param non-empty-string|null $folder The folder within the namespace that we should
|
||||
* look for the file. If $file does not contain
|
||||
* this value, it will be appended to the namespace
|
||||
* folder.
|
||||
* @param string $ext The file extension the file should have.
|
||||
*
|
||||
* @return false|string The path to the file, or false if not found.
|
||||
*/
|
||||
public function locateFile(string $file, ?string $folder = null, string $ext = 'php')
|
||||
{
|
||||
$file = $this->ensureExt($file, $ext);
|
||||
|
||||
// Clears the folder name if it is at the beginning of the filename
|
||||
if ($folder !== null && str_starts_with($file, $folder)) {
|
||||
$file = substr($file, strlen($folder . '/'));
|
||||
}
|
||||
|
||||
// Is not namespaced? Try the application folder.
|
||||
if (! str_contains($file, '\\')) {
|
||||
return $this->legacyLocate($file, $folder);
|
||||
}
|
||||
|
||||
// Standardize slashes to handle nested directories.
|
||||
$file = strtr($file, '/', '\\');
|
||||
$file = ltrim($file, '\\');
|
||||
|
||||
$segments = explode('\\', $file);
|
||||
|
||||
// The first segment will be empty if a slash started the filename.
|
||||
if ($segments[0] === '') {
|
||||
unset($segments[0]);
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
$filename = '';
|
||||
|
||||
// Namespaces always comes with arrays of paths
|
||||
$namespaces = $this->autoloader->getNamespace();
|
||||
|
||||
foreach (array_keys($namespaces) as $namespace) {
|
||||
if (substr($file, 0, strlen($namespace) + 1) === $namespace . '\\') {
|
||||
$fileWithoutNamespace = substr($file, strlen($namespace));
|
||||
|
||||
// There may be sub-namespaces of the same vendor,
|
||||
// so overwrite them with namespaces found later.
|
||||
$paths = $namespaces[$namespace];
|
||||
$filename = ltrim(str_replace('\\', '/', $fileWithoutNamespace), '/');
|
||||
}
|
||||
}
|
||||
|
||||
// if no namespaces matched then quit
|
||||
if ($paths === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each path in the namespace
|
||||
foreach ($paths as $path) {
|
||||
// Ensure trailing slash
|
||||
$path = rtrim($path, '/') . '/';
|
||||
|
||||
// If we have a folder name, then the calling function
|
||||
// expects this file to be within that folder, like 'Views',
|
||||
// or 'libraries'.
|
||||
if ($folder !== null && ! str_contains($path . $filename, '/' . $folder . '/')) {
|
||||
$path .= trim($folder, '/') . '/';
|
||||
}
|
||||
|
||||
$path .= $filename;
|
||||
if (is_file($path)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines a file and returns the fully qualified class name.
|
||||
*/
|
||||
public function getClassname(string $file): string
|
||||
{
|
||||
if (is_dir($file)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$php = file_get_contents($file);
|
||||
$tokens = token_get_all($php);
|
||||
$dlm = false;
|
||||
$namespace = '';
|
||||
$className = '';
|
||||
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($i < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === 'phpnamespace' || $tokens[$i - 2][1] === 'namespace')) || ($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $token[0] === T_STRING)) {
|
||||
if (! $dlm) {
|
||||
$namespace = '';
|
||||
}
|
||||
|
||||
if (isset($token[1])) {
|
||||
$namespace = $namespace !== '' ? $namespace . '\\' . $token[1] : $token[1];
|
||||
$dlm = true;
|
||||
}
|
||||
} elseif ($dlm && ($token[0] !== T_NS_SEPARATOR) && ($token[0] !== T_STRING)) {
|
||||
$dlm = false;
|
||||
}
|
||||
|
||||
if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === 'phpclass'))
|
||||
&& $tokens[$i - 1][0] === T_WHITESPACE
|
||||
&& $token[0] === T_STRING) {
|
||||
$className = $token[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($className === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all of the defined namespaces looking for a file.
|
||||
* Returns an array of all found locations for the defined file.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $locator->search('Config/Routes.php');
|
||||
* // Assuming PSR4 namespaces include foo and bar, might return:
|
||||
* [
|
||||
* 'app/Modules/foo/Config/Routes.php',
|
||||
* 'app/Modules/bar/Config/Routes.php',
|
||||
* ]
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array
|
||||
{
|
||||
$path = $this->ensureExt($path, $ext);
|
||||
|
||||
$foundPaths = [];
|
||||
$appPaths = [];
|
||||
|
||||
foreach ($this->getNamespaces() as $namespace) {
|
||||
if (isset($namespace['path']) && is_file($namespace['path'] . $path)) {
|
||||
$fullPath = $namespace['path'] . $path;
|
||||
$resolvedPath = realpath($fullPath);
|
||||
$fullPath = $resolvedPath !== false ? $resolvedPath : $fullPath;
|
||||
|
||||
if ($prioritizeApp) {
|
||||
$foundPaths[] = $fullPath;
|
||||
} elseif (str_starts_with($fullPath, APPPATH)) {
|
||||
$appPaths[] = $fullPath;
|
||||
} else {
|
||||
$foundPaths[] = $fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $prioritizeApp && $appPaths !== []) {
|
||||
$foundPaths = [...$foundPaths, ...$appPaths];
|
||||
}
|
||||
|
||||
// Remove any duplicates
|
||||
return array_values(array_unique($foundPaths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a extension is at the end of a filename
|
||||
*/
|
||||
protected function ensureExt(string $path, string $ext): string
|
||||
{
|
||||
if ($ext !== '') {
|
||||
$ext = '.' . $ext;
|
||||
|
||||
if (! str_ends_with($path, $ext)) {
|
||||
$path .= $ext;
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace mappings we know about.
|
||||
*
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
protected function getNamespaces()
|
||||
{
|
||||
$namespaces = [];
|
||||
|
||||
// Save system for last
|
||||
$system = [];
|
||||
|
||||
foreach ($this->autoloader->getNamespace() as $prefix => $paths) {
|
||||
foreach ($paths as $path) {
|
||||
if ($prefix === 'CodeIgniter') {
|
||||
$system[] = [
|
||||
'prefix' => $prefix,
|
||||
'path' => rtrim($path, '\\/') . DIRECTORY_SEPARATOR,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespaces[] = [
|
||||
'prefix' => $prefix,
|
||||
'path' => rtrim($path, '\\/') . DIRECTORY_SEPARATOR,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($namespaces, $system);
|
||||
}
|
||||
|
||||
public function findQualifiedNameFromPath(string $path)
|
||||
{
|
||||
$resolvedPath = realpath($path);
|
||||
$path = $resolvedPath !== false ? $resolvedPath : $path;
|
||||
|
||||
if (! is_file($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->getNamespaces() as $namespace) {
|
||||
$resolvedNamespacePath = realpath($namespace['path']);
|
||||
$namespace['path'] = $resolvedNamespacePath !== false ? $resolvedNamespacePath : $namespace['path'];
|
||||
|
||||
if ($namespace['path'] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mb_strpos($path, $namespace['path']) === 0) {
|
||||
$className = $namespace['prefix'] . '\\' .
|
||||
ltrim(
|
||||
str_replace(
|
||||
'/',
|
||||
'\\',
|
||||
mb_substr($path, mb_strlen($namespace['path'])),
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
|
||||
// Remove the file extension (.php)
|
||||
/** @var class-string */
|
||||
$className = mb_substr($className, 0, -4);
|
||||
|
||||
if (in_array($className, $this->invalidClassnames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this exists
|
||||
if (class_exists($className)) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
// If the class does not exist, it is an invalid classname.
|
||||
$this->invalidClassnames[] = $className;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the defined namespaces, returning a list of all files
|
||||
* that are contained within the subpath specified by $path.
|
||||
*
|
||||
* @return list<string> List of file paths
|
||||
*/
|
||||
public function listFiles(string $path): array
|
||||
{
|
||||
if ($path === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = [];
|
||||
helper('filesystem');
|
||||
|
||||
foreach ($this->getNamespaces() as $namespace) {
|
||||
$fullPath = $namespace['path'] . $path;
|
||||
$resolvedPath = realpath($fullPath);
|
||||
$fullPath = $resolvedPath !== false ? $resolvedPath : $fullPath;
|
||||
|
||||
if (! is_dir($fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tempFiles = get_filenames($fullPath, true, false, false);
|
||||
|
||||
if ($tempFiles !== []) {
|
||||
$files = array_merge($files, $tempFiles);
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the provided namespace, returning a list of all files
|
||||
* that are contained within the sub path specified by $path.
|
||||
*
|
||||
* @return list<string> List of file paths
|
||||
*/
|
||||
public function listNamespaceFiles(string $prefix, string $path): array
|
||||
{
|
||||
if ($path === '' || ($prefix === '')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = [];
|
||||
helper('filesystem');
|
||||
|
||||
// autoloader->getNamespace($prefix) returns an array of paths for that namespace
|
||||
foreach ($this->autoloader->getNamespace($prefix) as $namespacePath) {
|
||||
$fullPath = rtrim($namespacePath, '/') . '/' . $path;
|
||||
$resolvedPath = realpath($fullPath);
|
||||
$fullPath = $resolvedPath !== false ? $resolvedPath : $fullPath;
|
||||
|
||||
if (! is_dir($fullPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tempFiles = get_filenames($fullPath, true, false, false);
|
||||
|
||||
if ($tempFiles !== []) {
|
||||
$files = array_merge($files, $tempFiles);
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the app folder to see if the file can be found.
|
||||
* Only for use with filenames that DO NOT include namespacing.
|
||||
*
|
||||
* @param non-empty-string|null $folder
|
||||
*
|
||||
* @return false|string The path to the file, or false if not found.
|
||||
*/
|
||||
protected function legacyLocate(string $file, ?string $folder = null)
|
||||
{
|
||||
$path = APPPATH . ($folder === null ? $file : $folder . '/' . $file);
|
||||
$resolvedPath = realpath($path);
|
||||
$path = $resolvedPath !== false ? $resolvedPath : $path;
|
||||
|
||||
if (is_file($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?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\Autoloader;
|
||||
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Cache\FactoriesCache\FileVarExportHandler;
|
||||
|
||||
/**
|
||||
* FileLocator with Cache
|
||||
*
|
||||
* @see \CodeIgniter\Autoloader\FileLocatorCachedTest
|
||||
*/
|
||||
final class FileLocatorCached implements FileLocatorInterface
|
||||
{
|
||||
/**
|
||||
* @var CacheInterface|FileVarExportHandler
|
||||
*/
|
||||
private $cacheHandler;
|
||||
|
||||
/**
|
||||
* Cache data
|
||||
*
|
||||
* [method => data]
|
||||
* E.g.,
|
||||
* [
|
||||
* 'search' => [$path => $foundPaths],
|
||||
* ]
|
||||
*
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
private array $cache = [];
|
||||
|
||||
/**
|
||||
* Is the cache updated?
|
||||
*/
|
||||
private bool $cacheUpdated = false;
|
||||
|
||||
private string $cacheKey = 'FileLocatorCache';
|
||||
|
||||
/**
|
||||
* @param CacheInterface|FileVarExportHandler|null $cache
|
||||
*/
|
||||
public function __construct(private readonly FileLocator $locator, $cache = null)
|
||||
{
|
||||
$this->cacheHandler = $cache ?? new FileVarExportHandler();
|
||||
|
||||
$this->loadCache();
|
||||
}
|
||||
|
||||
private function loadCache(): void
|
||||
{
|
||||
$data = $this->cacheHandler->get($this->cacheKey);
|
||||
|
||||
if (is_array($data)) {
|
||||
$this->cache = $data;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->saveCache();
|
||||
}
|
||||
|
||||
private function saveCache(): void
|
||||
{
|
||||
if ($this->cacheUpdated) {
|
||||
$this->cacheHandler->save($this->cacheKey, $this->cache, 3600 * 24);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache data
|
||||
*/
|
||||
public function deleteCache(): void
|
||||
{
|
||||
$this->cacheUpdated = false;
|
||||
$this->cacheHandler->delete($this->cacheKey);
|
||||
}
|
||||
|
||||
public function findQualifiedNameFromPath(string $path): false|string
|
||||
{
|
||||
if (isset($this->cache['findQualifiedNameFromPath'][$path])) {
|
||||
return $this->cache['findQualifiedNameFromPath'][$path];
|
||||
}
|
||||
|
||||
$classname = $this->locator->findQualifiedNameFromPath($path);
|
||||
|
||||
$this->cache['findQualifiedNameFromPath'][$path] = $classname;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $classname;
|
||||
}
|
||||
|
||||
public function getClassname(string $file): string
|
||||
{
|
||||
if (isset($this->cache['getClassname'][$file])) {
|
||||
return $this->cache['getClassname'][$file];
|
||||
}
|
||||
|
||||
$classname = $this->locator->getClassname($file);
|
||||
|
||||
$this->cache['getClassname'][$file] = $classname;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $classname;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array
|
||||
{
|
||||
if (isset($this->cache['search'][$path][$ext][$prioritizeApp])) {
|
||||
return $this->cache['search'][$path][$ext][$prioritizeApp];
|
||||
}
|
||||
|
||||
$foundPaths = $this->locator->search($path, $ext, $prioritizeApp);
|
||||
|
||||
$this->cache['search'][$path][$ext][$prioritizeApp] = $foundPaths;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $foundPaths;
|
||||
}
|
||||
|
||||
public function listFiles(string $path): array
|
||||
{
|
||||
if (isset($this->cache['listFiles'][$path])) {
|
||||
return $this->cache['listFiles'][$path];
|
||||
}
|
||||
|
||||
$files = $this->locator->listFiles($path);
|
||||
|
||||
$this->cache['listFiles'][$path] = $files;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function listNamespaceFiles(string $prefix, string $path): array
|
||||
{
|
||||
if (isset($this->cache['listNamespaceFiles'][$prefix][$path])) {
|
||||
return $this->cache['listNamespaceFiles'][$prefix][$path];
|
||||
}
|
||||
|
||||
$files = $this->locator->listNamespaceFiles($prefix, $path);
|
||||
|
||||
$this->cache['listNamespaceFiles'][$prefix][$path] = $files;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function locateFile(string $file, ?string $folder = null, string $ext = 'php'): false|string
|
||||
{
|
||||
if (isset($this->cache['locateFile'][$file][$folder][$ext])) {
|
||||
return $this->cache['locateFile'][$file][$folder][$ext];
|
||||
}
|
||||
|
||||
$files = $this->locator->locateFile($file, $folder, $ext);
|
||||
|
||||
$this->cache['locateFile'][$file][$folder][$ext] = $files;
|
||||
$this->cacheUpdated = true;
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?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\Autoloader;
|
||||
|
||||
/**
|
||||
* Allows loading non-class files in a namespaced manner.
|
||||
* Works with Helpers, Views, etc.
|
||||
*/
|
||||
interface FileLocatorInterface
|
||||
{
|
||||
/**
|
||||
* Attempts to locate a file by examining the name for a namespace
|
||||
* and looking through the PSR-4 namespaced files that we know about.
|
||||
*
|
||||
* @param string $file The relative file path or namespaced file to
|
||||
* locate. If not namespaced, search in the app
|
||||
* folder.
|
||||
* @param non-empty-string|null $folder The folder within the namespace that we should
|
||||
* look for the file. If $file does not contain
|
||||
* this value, it will be appended to the namespace
|
||||
* folder.
|
||||
* @param string $ext The file extension the file should have.
|
||||
*
|
||||
* @return false|string The path to the file, or false if not found.
|
||||
*/
|
||||
public function locateFile(string $file, ?string $folder = null, string $ext = 'php');
|
||||
|
||||
/**
|
||||
* Examines a file and returns the fully qualified class name.
|
||||
*/
|
||||
public function getClassname(string $file): string;
|
||||
|
||||
/**
|
||||
* Searches through all of the defined namespaces looking for a file.
|
||||
* Returns an array of all found locations for the defined file.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $locator->search('Config/Routes.php');
|
||||
* // Assuming PSR4 namespaces include foo and bar, might return:
|
||||
* [
|
||||
* 'app/Modules/foo/Config/Routes.php',
|
||||
* 'app/Modules/bar/Config/Routes.php',
|
||||
* ]
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array;
|
||||
|
||||
/**
|
||||
* Find the qualified name of a file according to
|
||||
* the namespace of the first matched namespace path.
|
||||
*
|
||||
* @return class-string|false The qualified name or false if the path is not found
|
||||
*/
|
||||
public function findQualifiedNameFromPath(string $path);
|
||||
|
||||
/**
|
||||
* Scans the defined namespaces, returning a list of all files
|
||||
* that are contained within the subpath specified by $path.
|
||||
*
|
||||
* @return list<string> List of file paths
|
||||
*/
|
||||
public function listFiles(string $path): array;
|
||||
|
||||
/**
|
||||
* Scans the provided namespace, returning a list of all files
|
||||
* that are contained within the sub path specified by $path.
|
||||
*
|
||||
* @return list<string> List of file paths
|
||||
*/
|
||||
public function listNamespaceFiles(string $prefix, string $path): array;
|
||||
}
|
||||
+1954
File diff suppressed because it is too large
Load Diff
+364
@@ -0,0 +1,364 @@
|
||||
<?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;
|
||||
|
||||
use CodeIgniter\Cache\FactoriesCache;
|
||||
use CodeIgniter\CLI\Console;
|
||||
use CodeIgniter\Config\DotEnv;
|
||||
use Config\Autoload;
|
||||
use Config\Modules;
|
||||
use Config\Optimize;
|
||||
use Config\Paths;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
* Bootstrap for the application
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Boot
|
||||
{
|
||||
/**
|
||||
* Used by `public/index.php`
|
||||
*
|
||||
* Context
|
||||
* web: Invoked by HTTP request
|
||||
* php-cli: Invoked by CLI via `php public/index.php`
|
||||
*
|
||||
* @return int Exit code.
|
||||
*/
|
||||
public static function bootWeb(Paths $paths): int
|
||||
{
|
||||
static::definePathConstants($paths);
|
||||
if (! defined('APP_NAMESPACE')) {
|
||||
static::loadConstants();
|
||||
}
|
||||
static::checkMissingExtensions();
|
||||
|
||||
static::loadDotEnv($paths);
|
||||
static::defineEnvironment();
|
||||
static::loadEnvironmentBootstrap($paths);
|
||||
|
||||
static::loadCommonFunctions();
|
||||
static::loadAutoloader();
|
||||
static::setExceptionHandler();
|
||||
static::initializeKint();
|
||||
|
||||
$configCacheEnabled = class_exists(Optimize::class)
|
||||
&& (new Optimize())->configCacheEnabled;
|
||||
if ($configCacheEnabled) {
|
||||
$factoriesCache = static::loadConfigCache();
|
||||
}
|
||||
|
||||
static::autoloadHelpers();
|
||||
|
||||
$app = static::initializeCodeIgniter();
|
||||
static::runCodeIgniter($app);
|
||||
|
||||
if ($configCacheEnabled) {
|
||||
static::saveConfigCache($factoriesCache);
|
||||
}
|
||||
|
||||
// Exits the application, setting the exit code for CLI-based
|
||||
// applications that might be watching.
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by `spark`
|
||||
*
|
||||
* @return int Exit code.
|
||||
*/
|
||||
public static function bootSpark(Paths $paths): int
|
||||
{
|
||||
static::definePathConstants($paths);
|
||||
if (! defined('APP_NAMESPACE')) {
|
||||
static::loadConstants();
|
||||
}
|
||||
static::checkMissingExtensions();
|
||||
|
||||
static::loadDotEnv($paths);
|
||||
static::defineEnvironment();
|
||||
static::loadEnvironmentBootstrap($paths);
|
||||
|
||||
static::loadCommonFunctions();
|
||||
static::loadAutoloader();
|
||||
static::setExceptionHandler();
|
||||
static::initializeKint();
|
||||
static::autoloadHelpers();
|
||||
|
||||
static::initializeCodeIgniter();
|
||||
$console = static::initializeConsole();
|
||||
|
||||
return static::runCommand($console);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by `system/Test/bootstrap.php`
|
||||
*/
|
||||
public static function bootTest(Paths $paths): void
|
||||
{
|
||||
static::loadConstants();
|
||||
static::checkMissingExtensions();
|
||||
|
||||
static::loadDotEnv($paths);
|
||||
static::loadEnvironmentBootstrap($paths, false);
|
||||
|
||||
static::loadCommonFunctions();
|
||||
static::loadAutoloader();
|
||||
static::setExceptionHandler();
|
||||
static::initializeKint();
|
||||
static::autoloadHelpers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by `preload.php`
|
||||
*/
|
||||
public static function preload(Paths $paths): void
|
||||
{
|
||||
static::definePathConstants($paths);
|
||||
static::loadConstants();
|
||||
static::defineEnvironment();
|
||||
static::loadEnvironmentBootstrap($paths, false);
|
||||
|
||||
static::loadAutoloader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment settings from .env files into $_SERVER and $_ENV
|
||||
*/
|
||||
protected static function loadDotEnv(Paths $paths): void
|
||||
{
|
||||
require_once $paths->systemDirectory . '/Config/DotEnv.php';
|
||||
(new DotEnv($paths->appDirectory . '/../'))->load();
|
||||
}
|
||||
|
||||
protected static function defineEnvironment(): void
|
||||
{
|
||||
if (! defined('ENVIRONMENT')) {
|
||||
// @phpstan-ignore-next-line
|
||||
$env = $_ENV['CI_ENVIRONMENT'] ?? $_SERVER['CI_ENVIRONMENT']
|
||||
?? getenv('CI_ENVIRONMENT')
|
||||
?: 'production';
|
||||
|
||||
define('ENVIRONMENT', $env);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function loadEnvironmentBootstrap(Paths $paths, bool $exit = true): void
|
||||
{
|
||||
if (is_file($paths->appDirectory . '/Config/Boot/' . ENVIRONMENT . '.php')) {
|
||||
require_once $paths->appDirectory . '/Config/Boot/' . ENVIRONMENT . '.php';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($exit) {
|
||||
header('HTTP/1.1 503 Service Unavailable.', true, 503);
|
||||
echo 'The application environment is not set correctly.';
|
||||
|
||||
exit(EXIT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The path constants provide convenient access to the folders throughout
|
||||
* the application. We have to set them up here, so they are available in
|
||||
* the config files that are loaded.
|
||||
*/
|
||||
protected static function definePathConstants(Paths $paths): void
|
||||
{
|
||||
// The path to the application directory.
|
||||
if (! defined('APPPATH')) {
|
||||
define('APPPATH', realpath(rtrim($paths->appDirectory, '\\/ ')) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
// The path to the project root directory. Just above APPPATH.
|
||||
if (! defined('ROOTPATH')) {
|
||||
define('ROOTPATH', realpath(APPPATH . '../') . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
// The path to the system directory.
|
||||
if (! defined('SYSTEMPATH')) {
|
||||
define('SYSTEMPATH', realpath(rtrim($paths->systemDirectory, '\\/ ')) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
// The path to the writable directory.
|
||||
if (! defined('WRITEPATH')) {
|
||||
$writePath = realpath(rtrim($paths->writableDirectory, '\\/ '));
|
||||
|
||||
if ($writePath === false) {
|
||||
header('HTTP/1.1 503 Service Unavailable.', true, 503);
|
||||
echo 'The WRITEPATH is not set correctly.';
|
||||
|
||||
// EXIT_ERROR is not yet defined
|
||||
exit(1);
|
||||
}
|
||||
define('WRITEPATH', $writePath . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
// The path to the tests directory
|
||||
if (! defined('TESTPATH')) {
|
||||
define('TESTPATH', realpath(rtrim($paths->testsDirectory, '\\/ ')) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function loadConstants(): void
|
||||
{
|
||||
require_once APPPATH . 'Config/Constants.php';
|
||||
}
|
||||
|
||||
protected static function loadCommonFunctions(): void
|
||||
{
|
||||
// Require app/Common.php file if exists.
|
||||
if (is_file(APPPATH . 'Common.php')) {
|
||||
require_once APPPATH . 'Common.php';
|
||||
}
|
||||
|
||||
// Require system/Common.php
|
||||
require_once SYSTEMPATH . 'Common.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* The autoloader allows all the pieces to work together in the framework.
|
||||
* We have to load it here, though, so that the config files can use the
|
||||
* path constants.
|
||||
*/
|
||||
protected static function loadAutoloader(): void
|
||||
{
|
||||
if (! class_exists(Autoload::class, false)) {
|
||||
require_once SYSTEMPATH . 'Config/AutoloadConfig.php';
|
||||
require_once APPPATH . 'Config/Autoload.php';
|
||||
require_once SYSTEMPATH . 'Modules/Modules.php';
|
||||
require_once APPPATH . 'Config/Modules.php';
|
||||
}
|
||||
|
||||
require_once SYSTEMPATH . 'Autoloader/Autoloader.php';
|
||||
require_once SYSTEMPATH . 'Config/BaseService.php';
|
||||
require_once SYSTEMPATH . 'Config/Services.php';
|
||||
require_once APPPATH . 'Config/Services.php';
|
||||
|
||||
// Initialize and register the loader with the SPL autoloader stack.
|
||||
Services::autoloader()->initialize(new Autoload(), new Modules())->register();
|
||||
}
|
||||
|
||||
protected static function autoloadHelpers(): void
|
||||
{
|
||||
service('autoloader')->loadHelpers();
|
||||
}
|
||||
|
||||
protected static function setExceptionHandler(): void
|
||||
{
|
||||
service('exceptions')->initialize();
|
||||
}
|
||||
|
||||
protected static function checkMissingExtensions(): void
|
||||
{
|
||||
if (is_file(COMPOSER_PATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run this check for manual installations
|
||||
$missingExtensions = [];
|
||||
|
||||
foreach ([
|
||||
'intl',
|
||||
'json',
|
||||
'mbstring',
|
||||
] as $extension) {
|
||||
if (! extension_loaded($extension)) {
|
||||
$missingExtensions[] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingExtensions === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'The framework needs the following extension(s) installed and loaded: %s.',
|
||||
implode(', ', $missingExtensions),
|
||||
);
|
||||
|
||||
header('HTTP/1.1 503 Service Unavailable.', true, 503);
|
||||
echo $message;
|
||||
|
||||
exit(EXIT_ERROR);
|
||||
}
|
||||
|
||||
protected static function initializeKint(): void
|
||||
{
|
||||
service('autoloader')->initializeKint(CI_DEBUG);
|
||||
}
|
||||
|
||||
protected static function loadConfigCache(): FactoriesCache
|
||||
{
|
||||
$factoriesCache = new FactoriesCache();
|
||||
$factoriesCache->load('config');
|
||||
|
||||
return $factoriesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* The CodeIgniter class contains the core functionality to make
|
||||
* the application run, and does all the dirty work to get
|
||||
* the pieces all working together.
|
||||
*/
|
||||
protected static function initializeCodeIgniter(): CodeIgniter
|
||||
{
|
||||
$app = service('codeigniter');
|
||||
$app->initialize();
|
||||
$context = is_cli() ? 'php-cli' : 'web';
|
||||
$app->setContext($context);
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Now that everything is set up, it's time to actually fire
|
||||
* up the engines and make this app do its thang.
|
||||
*/
|
||||
protected static function runCodeIgniter(CodeIgniter $app): void
|
||||
{
|
||||
$app->run();
|
||||
}
|
||||
|
||||
protected static function saveConfigCache(FactoriesCache $factoriesCache): void
|
||||
{
|
||||
$factoriesCache->save('config');
|
||||
}
|
||||
|
||||
protected static function initializeConsole(): Console
|
||||
{
|
||||
$console = new Console();
|
||||
|
||||
// Show basic information before we do anything else.
|
||||
// @phpstan-ignore-next-line
|
||||
if (is_int($suppress = array_search('--no-header', $_SERVER['argv'], true))) {
|
||||
unset($_SERVER['argv'][$suppress]); // @phpstan-ignore-line
|
||||
$suppress = true;
|
||||
}
|
||||
|
||||
$console->showHeader($suppress);
|
||||
|
||||
return $console;
|
||||
}
|
||||
|
||||
protected static function runCommand(Console $console): int
|
||||
{
|
||||
$exit = $console->run();
|
||||
|
||||
return is_int($exit) ? $exit : EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?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\CLI;
|
||||
|
||||
use Config\Exceptions;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* BaseCommand is the base class used in creating CLI commands.
|
||||
*
|
||||
* @property array<string, string> $arguments
|
||||
* @property Commands $commands
|
||||
* @property string $description
|
||||
* @property string $group
|
||||
* @property LoggerInterface $logger
|
||||
* @property string $name
|
||||
* @property array<string, string> $options
|
||||
* @property string $usage
|
||||
*/
|
||||
abstract class BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* the Command's usage description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage;
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* the Command's options description
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* the Command's Arguments description
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The Logger to use for a command
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Instance of Commands so
|
||||
* commands can call other commands.
|
||||
*
|
||||
* @var Commands
|
||||
*/
|
||||
protected $commands;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Commands $commands)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->commands = $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int|void
|
||||
*/
|
||||
abstract public function run(array $params);
|
||||
|
||||
/**
|
||||
* Can be used by a command to run other commands.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int|void
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
protected function call(string $command, array $params = [])
|
||||
{
|
||||
return $this->commands->run($command, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to display an error with line/file, in child commands.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function showError(Throwable $e)
|
||||
{
|
||||
$exception = $e;
|
||||
$message = $e->getMessage();
|
||||
$config = config(Exceptions::class);
|
||||
|
||||
require $config->errorViewPath . '/cli/error_exception.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Help includes (Usage, Arguments, Description, Options).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showHelp()
|
||||
{
|
||||
CLI::write(lang('CLI.helpUsage'), 'yellow');
|
||||
|
||||
if (isset($this->usage)) {
|
||||
$usage = $this->usage;
|
||||
} else {
|
||||
$usage = $this->name;
|
||||
|
||||
if ($this->arguments !== []) {
|
||||
$usage .= ' [arguments]';
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write($this->setPad($usage, 0, 0, 2));
|
||||
|
||||
if (isset($this->description)) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpDescription'), 'yellow');
|
||||
CLI::write($this->setPad($this->description, 0, 0, 2));
|
||||
}
|
||||
|
||||
if ($this->arguments !== []) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpArguments'), 'yellow');
|
||||
$length = max(array_map(strlen(...), array_keys($this->arguments)));
|
||||
|
||||
foreach ($this->arguments as $argument => $description) {
|
||||
CLI::write(CLI::color($this->setPad($argument, $length, 2, 2), 'green') . $description);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->options !== []) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpOptions'), 'yellow');
|
||||
$length = max(array_map(strlen(...), array_keys($this->options)));
|
||||
|
||||
foreach ($this->options as $option => $description) {
|
||||
CLI::write(CLI::color($this->setPad($option, $length, 2, 2), 'green') . $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads our string out so that all titles are the same length to nicely line up descriptions.
|
||||
*
|
||||
* @param int $extra How many extra spaces to add at the end
|
||||
*/
|
||||
public function setPad(string $item, int $max, int $extra = 2, int $indent = 0): string
|
||||
{
|
||||
$max += $extra + $indent;
|
||||
|
||||
return str_pad(str_repeat(' ', $indent) . $item, $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pad for $key => $value array output
|
||||
*
|
||||
* @param array<string, string> $array
|
||||
*
|
||||
* @deprecated Use setPad() instead.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getPad(array $array, int $pad): int
|
||||
{
|
||||
$max = 0;
|
||||
|
||||
foreach (array_keys($array) as $key) {
|
||||
$max = max($max, strlen($key));
|
||||
}
|
||||
|
||||
return $max + $pad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it simple to access our protected properties.
|
||||
*
|
||||
* @return array<string, string>|Commands|LoggerInterface|string|null
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->{$key} ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it simple to check our protected properties.
|
||||
*/
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->{$key});
|
||||
}
|
||||
}
|
||||
+1155
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
<?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\CLI;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Log\Logger;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Core functionality for running, listing, etc commands.
|
||||
*
|
||||
* @phpstan-type commands_list array<string, array{'class': class-string<BaseCommand>, 'file': string, 'group': string,'description': string}>
|
||||
*/
|
||||
class Commands
|
||||
{
|
||||
/**
|
||||
* The found commands.
|
||||
*
|
||||
* @var commands_list
|
||||
*/
|
||||
protected $commands = [];
|
||||
|
||||
/**
|
||||
* Logger instance.
|
||||
*
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Logger|null $logger
|
||||
*/
|
||||
public function __construct($logger = null)
|
||||
{
|
||||
$this->logger = $logger ?? service('logger');
|
||||
$this->discoverCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a command given
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function run(string $command, array $params)
|
||||
{
|
||||
if (! $this->verifyCommand($command, $this->commands)) {
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
// The file would have already been loaded during the
|
||||
// createCommandList function...
|
||||
$className = $this->commands[$command]['class'];
|
||||
$class = new $className($this->logger, $this);
|
||||
|
||||
Events::trigger('pre_command');
|
||||
|
||||
$exit = $class->run($params);
|
||||
|
||||
Events::trigger('post_command');
|
||||
|
||||
return $exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the list of commands.
|
||||
*
|
||||
* @return commands_list
|
||||
*/
|
||||
public function getCommands()
|
||||
{
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all commands in the framework and within user code,
|
||||
* and collects instances of them to work with.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function discoverCommands()
|
||||
{
|
||||
if ($this->commands !== []) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var FileLocatorInterface */
|
||||
$locator = service('locator');
|
||||
$files = $locator->listFiles('Commands/');
|
||||
|
||||
// If no matching command files were found, bail
|
||||
// This should never happen in unit testing.
|
||||
if ($files === []) {
|
||||
return; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// Loop over each file checking to see if a command with that
|
||||
// alias exists in the class.
|
||||
foreach ($files as $file) {
|
||||
/** @var class-string<BaseCommand>|false */
|
||||
$className = $locator->findQualifiedNameFromPath($file);
|
||||
|
||||
if ($className === false || ! class_exists($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$class = new ReflectionClass($className);
|
||||
|
||||
if (! $class->isInstantiable() || ! $class->isSubclassOf(BaseCommand::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = new $className($this->logger, $this);
|
||||
|
||||
if (isset($class->group) && ! isset($this->commands[$class->name])) {
|
||||
$this->commands[$class->name] = [
|
||||
'class' => $className,
|
||||
'file' => $file,
|
||||
'group' => $class->group,
|
||||
'description' => $class->description,
|
||||
];
|
||||
}
|
||||
|
||||
unset($class);
|
||||
} catch (ReflectionException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
asort($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the command being sought is found
|
||||
* in the commands list.
|
||||
*
|
||||
* @param commands_list $commands
|
||||
*/
|
||||
public function verifyCommand(string $command, array $commands): bool
|
||||
{
|
||||
if (isset($commands[$command])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = lang('CLI.commandNotFound', [$command]);
|
||||
$alternatives = $this->getCommandAlternatives($command, $commands);
|
||||
|
||||
if ($alternatives !== []) {
|
||||
if (count($alternatives) === 1) {
|
||||
$message .= "\n\n" . lang('CLI.altCommandSingular') . "\n ";
|
||||
} else {
|
||||
$message .= "\n\n" . lang('CLI.altCommandPlural') . "\n ";
|
||||
}
|
||||
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
CLI::error($message);
|
||||
CLI::newLine();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds alternative of `$name` among collection
|
||||
* of commands.
|
||||
*
|
||||
* @param commands_list $collection
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function getCommandAlternatives(string $name, array $collection): array
|
||||
{
|
||||
/** @var array<string, int> */
|
||||
$alternatives = [];
|
||||
|
||||
/** @var string $commandName */
|
||||
foreach (array_keys($collection) as $commandName) {
|
||||
$lev = levenshtein($name, $commandName);
|
||||
|
||||
if ($lev <= strlen($commandName) / 3 || str_contains($commandName, $name)) {
|
||||
$alternatives[$commandName] = $lev;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
return array_keys($alternatives);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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\CLI;
|
||||
|
||||
use CodeIgniter\CodeIgniter;
|
||||
use Config\App;
|
||||
use Config\Services;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Console
|
||||
*
|
||||
* @see \CodeIgniter\CLI\ConsoleTest
|
||||
*/
|
||||
class Console
|
||||
{
|
||||
/**
|
||||
* Runs the current command discovered on the CLI.
|
||||
*
|
||||
* @return int|void Exit code
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// Create CLIRequest
|
||||
$appConfig = config(App::class);
|
||||
Services::createRequest($appConfig, true);
|
||||
// Load Routes
|
||||
service('routes')->loadRoutes();
|
||||
|
||||
$params = array_merge(CLI::getSegments(), CLI::getOptions());
|
||||
$params = $this->parseParamsForHelpOption($params);
|
||||
$command = array_shift($params) ?? 'list';
|
||||
|
||||
return service('commands')->run($command, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays basic information about the Console.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showHeader(bool $suppress = false)
|
||||
{
|
||||
if ($suppress) {
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::write(sprintf(
|
||||
'CodeIgniter v%s Command Line Tool - Server Time: %s UTC%s',
|
||||
CodeIgniter::CI_VERSION,
|
||||
date('Y-m-d H:i:s'),
|
||||
date('P'),
|
||||
), 'green');
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspects the `$params` passed for presence of the
|
||||
* `--help` option.
|
||||
*
|
||||
* If present, it will be found as `['help' => null]`.
|
||||
* We'll remove that as an option from `$params` and
|
||||
* unshift it as argument instead.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
private function parseParamsForHelpOption(array $params): array
|
||||
{
|
||||
if (array_key_exists('help', $params)) {
|
||||
unset($params['help']);
|
||||
|
||||
$params = $params === [] ? ['list'] : $params;
|
||||
array_unshift($params, 'help');
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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\CLI\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\DebugTraceableTrait;
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* CLIException
|
||||
*/
|
||||
class CLIException extends RuntimeException
|
||||
{
|
||||
use DebugTraceableTrait;
|
||||
|
||||
/**
|
||||
* Thrown when `$color` specified for `$type` is not within the
|
||||
* allowed list of colors.
|
||||
*
|
||||
* @return CLIException
|
||||
*/
|
||||
public static function forInvalidColor(string $type, string $color)
|
||||
{
|
||||
return new static(lang('CLI.invalidColor', [$type, $color]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
<?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\CLI;
|
||||
|
||||
use Config\Generators;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* GeneratorTrait contains a collection of methods
|
||||
* to build the commands that generates a file.
|
||||
*/
|
||||
trait GeneratorTrait
|
||||
{
|
||||
/**
|
||||
* Component Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $component;
|
||||
|
||||
/**
|
||||
* File directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* (Optional) View template path
|
||||
*
|
||||
* We use special namespaced paths like:
|
||||
* `CodeIgniter\Commands\Generators\Views\cell.tpl.php`.
|
||||
*/
|
||||
protected ?string $templatePath = null;
|
||||
|
||||
/**
|
||||
* View template name for fallback
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* Language string key for required class names.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $classNameLang = '';
|
||||
|
||||
/**
|
||||
* Namespace to use for class.
|
||||
* Leave null to use the default namespace.
|
||||
*/
|
||||
protected ?string $namespace = null;
|
||||
|
||||
/**
|
||||
* Whether to require class name.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $hasClassName = true;
|
||||
|
||||
/**
|
||||
* Whether to sort class imports.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $sortImports = true;
|
||||
|
||||
/**
|
||||
* Whether the `--suffix` option has any effect.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $enabledSuffixing = true;
|
||||
|
||||
/**
|
||||
* The params array for easy access by other methods.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var array<int|string, string|null>
|
||||
*/
|
||||
private $params = [];
|
||||
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @deprecated use generateClass() instead
|
||||
*/
|
||||
protected function execute(array $params): void
|
||||
{
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a class file from an existing template.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function generateClass(array $params): void
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
// Get the fully qualified class name from the input.
|
||||
$class = $this->qualifyClassName();
|
||||
|
||||
// Get the file path from class name.
|
||||
$target = $this->buildPath($class);
|
||||
|
||||
// Check if path is empty.
|
||||
if ($target === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generateFile($target, $this->buildContent($class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a view file from an existing template.
|
||||
*
|
||||
* @param string $view namespaced view name that is generated
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function generateView(string $view, array $params): void
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
$target = $this->buildPath($view);
|
||||
|
||||
// Check if path is empty.
|
||||
if ($target === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generateFile($target, $this->buildContent($view));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing the file to disk, and all of the safety checks around that.
|
||||
*
|
||||
* @param string $target file path
|
||||
*/
|
||||
private function generateFile(string $target, string $content): void
|
||||
{
|
||||
if ($this->getOption('namespace') === 'CodeIgniter') {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::write(lang('CLI.generator.usingCINamespace'), 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
if (
|
||||
CLI::prompt(
|
||||
'Are you sure you want to continue?',
|
||||
['y', 'n'],
|
||||
'required',
|
||||
) === 'n'
|
||||
) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.generator.cancelOperation'), 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$isFile = is_file($target);
|
||||
|
||||
// Overwriting files unknowingly is a serious annoyance, So we'll check if
|
||||
// we are duplicating things, If 'force' option is not supplied, we bail.
|
||||
if (! $this->getOption('force') && $isFile) {
|
||||
CLI::error(
|
||||
lang('CLI.generator.fileExist', [clean_path($target)]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the directory to save the file is existing.
|
||||
$dir = dirname($target);
|
||||
|
||||
if (! is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
helper('filesystem');
|
||||
|
||||
// Build the class based on the details we have, We'll be getting our file
|
||||
// contents from the template, and then we'll do the necessary replacements.
|
||||
if (! write_file($target, $content)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error(
|
||||
lang('CLI.generator.fileError', [clean_path($target)]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($this->getOption('force') && $isFile) {
|
||||
CLI::write(
|
||||
lang('CLI.generator.fileOverwrite', [clean_path($target)]),
|
||||
'yellow',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::write(
|
||||
lang('CLI.generator.fileCreate', [clean_path($target)]),
|
||||
'green',
|
||||
);
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
*
|
||||
* @return string generated file content
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
return $this->parseTemplate($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change file basename before saving.
|
||||
*
|
||||
* Useful for components where the file name has a date.
|
||||
*/
|
||||
protected function basename(string $filename): string
|
||||
{
|
||||
return basename($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the class name and checks if it is already qualified.
|
||||
*/
|
||||
protected function qualifyClassName(): string
|
||||
{
|
||||
$class = $this->normalizeInputClassName();
|
||||
|
||||
// Gets the namespace from input. Don't forget the ending backslash!
|
||||
$namespace = $this->getNamespace() . '\\';
|
||||
|
||||
if (str_starts_with($class, $namespace)) {
|
||||
return $class; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$directoryString = ($this->directory !== null) ? $this->directory . '\\' : '';
|
||||
|
||||
return $namespace . $directoryString . str_replace('/', '\\', $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize input classname.
|
||||
*/
|
||||
private function normalizeInputClassName(): string
|
||||
{
|
||||
// Gets the class name from input.
|
||||
$class = $this->params[0] ?? CLI::getSegment(2);
|
||||
|
||||
if ($class === null && $this->hasClassName) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$nameLang = $this->classNameLang !== ''
|
||||
? $this->classNameLang
|
||||
: 'CLI.generator.className.default';
|
||||
$class = CLI::prompt(lang($nameLang), null, 'required');
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
helper('inflector');
|
||||
|
||||
$component = singular($this->component);
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/a5KNCR/2
|
||||
*/
|
||||
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component);
|
||||
|
||||
if (preg_match($pattern, $class, $matches) === 1) {
|
||||
$class = $matches[1] . ucfirst($matches[2]);
|
||||
}
|
||||
|
||||
if (
|
||||
$this->enabledSuffixing && $this->getOption('suffix')
|
||||
&& preg_match($pattern, $class) !== 1
|
||||
) {
|
||||
$class .= ucfirst($component);
|
||||
}
|
||||
|
||||
// Trims input, normalize separators, and ensure that all paths are in Pascalcase.
|
||||
return ltrim(
|
||||
implode(
|
||||
'\\',
|
||||
array_map(
|
||||
pascalize(...),
|
||||
explode('\\', str_replace('/', '\\', trim($class))),
|
||||
),
|
||||
),
|
||||
'\\/',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generator view as defined in the `Config\Generators::$views`,
|
||||
* with fallback to `$template` when the defined view does not exist.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
protected function renderTemplate(array $data = []): string
|
||||
{
|
||||
try {
|
||||
$template = $this->templatePath ?? config(Generators::class)->views[$this->name];
|
||||
|
||||
return view($template, $data, ['debug' => false]);
|
||||
} catch (Throwable $e) {
|
||||
log_message('error', (string) $e);
|
||||
|
||||
return view(
|
||||
"CodeIgniter\\Commands\\Generators\\Views\\{$this->template}",
|
||||
$data,
|
||||
['debug' => false],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs pseudo-variables contained within view file.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
* @param list<string> $search
|
||||
* @param list<string> $replace
|
||||
* @param array<string, bool|string|null> $data
|
||||
*
|
||||
* @return string generated file content
|
||||
*/
|
||||
protected function parseTemplate(
|
||||
string $class,
|
||||
array $search = [],
|
||||
array $replace = [],
|
||||
array $data = [],
|
||||
): string {
|
||||
// Retrieves the namespace part from the fully qualified class name.
|
||||
$namespace = trim(
|
||||
implode(
|
||||
'\\',
|
||||
array_slice(explode('\\', $class), 0, -1),
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
$search[] = '<@php';
|
||||
$search[] = '{namespace}';
|
||||
$search[] = '{class}';
|
||||
$replace[] = '<?php';
|
||||
$replace[] = $namespace;
|
||||
$replace[] = str_replace($namespace . '\\', '', $class);
|
||||
|
||||
return str_replace($search, $replace, $this->renderTemplate($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the contents for class being generated, doing all
|
||||
* the replacements necessary, and alphabetically sorts the
|
||||
* imports for a given template.
|
||||
*/
|
||||
protected function buildContent(string $class): string
|
||||
{
|
||||
$template = $this->prepare($class);
|
||||
|
||||
if (
|
||||
$this->sortImports
|
||||
&& preg_match(
|
||||
'/(?P<imports>(?:^use [^;]+;$\n?)+)/m',
|
||||
$template,
|
||||
$match,
|
||||
)
|
||||
) {
|
||||
$imports = explode("\n", trim($match['imports']));
|
||||
sort($imports);
|
||||
|
||||
return str_replace(trim($match['imports']), implode("\n", $imports), $template);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the file path from the class name.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
*/
|
||||
protected function buildPath(string $class): string
|
||||
{
|
||||
$namespace = $this->getNamespace();
|
||||
|
||||
// Check if the namespace is actually defined and we are not just typing gibberish.
|
||||
$base = service('autoloader')->getNamespace($namespace);
|
||||
|
||||
if (! $base = reset($base)) {
|
||||
CLI::error(
|
||||
lang('CLI.namespaceNotDefined', [$namespace]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$realpath = realpath($base);
|
||||
$base = ($realpath !== false) ? $realpath : $base;
|
||||
|
||||
$file = $base . DIRECTORY_SEPARATOR
|
||||
. str_replace(
|
||||
'\\',
|
||||
DIRECTORY_SEPARATOR,
|
||||
trim(str_replace($namespace . '\\', '', $class), '\\'),
|
||||
) . '.php';
|
||||
|
||||
return implode(
|
||||
DIRECTORY_SEPARATOR,
|
||||
array_slice(
|
||||
explode(DIRECTORY_SEPARATOR, $file),
|
||||
0,
|
||||
-1,
|
||||
),
|
||||
) . DIRECTORY_SEPARATOR . $this->basename($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace from the command-line option,
|
||||
* or the default namespace if the option is not set.
|
||||
* Can be overridden by directly setting $this->namespace.
|
||||
*/
|
||||
protected function getNamespace(): string
|
||||
{
|
||||
return $this->namespace ?? trim(
|
||||
str_replace(
|
||||
'/',
|
||||
'\\',
|
||||
$this->getOption('namespace') ?? APP_NAMESPACE,
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$hasClassName` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setHasClassName(bool $hasClassName)
|
||||
{
|
||||
$this->hasClassName = $hasClassName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$sortImports` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setSortImports(bool $sortImports)
|
||||
{
|
||||
$this->sortImports = $sortImports;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$enabledSuffixing` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setEnabledSuffixing(bool $enabledSuffixing)
|
||||
{
|
||||
$this->enabledSuffixing = $enabledSuffixing;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single command-line option. Returns TRUE if the option exists,
|
||||
* but doesn't have a value, and is simply acting as a flag.
|
||||
*/
|
||||
protected function getOption(string $name): bool|string|null
|
||||
{
|
||||
if (! array_key_exists($name, $this->params)) {
|
||||
return CLI::getOption($name);
|
||||
}
|
||||
|
||||
return $this->params[$name] ?? true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\CLI;
|
||||
|
||||
/**
|
||||
* Input and Output for CLI.
|
||||
*/
|
||||
class InputOutput
|
||||
{
|
||||
/**
|
||||
* Is the readline library on the system?
|
||||
*/
|
||||
private readonly bool $readlineSupport;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Readline is an extension for PHP that makes interactivity with PHP
|
||||
// much more bash-like.
|
||||
// http://www.php.net/manual/en/readline.installation.php
|
||||
$this->readlineSupport = extension_loaded('readline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input from the shell, using readline or the standard STDIN
|
||||
*
|
||||
* Named options must be in the following formats:
|
||||
* php index.php user -v --v -name=John --name=John
|
||||
*
|
||||
* @param string|null $prefix You may specify a string with which to prompt the user.
|
||||
*/
|
||||
public function input(?string $prefix = null): string
|
||||
{
|
||||
// readline() can't be tested.
|
||||
if ($this->readlineSupport && ENVIRONMENT !== 'testing') {
|
||||
return readline($prefix); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
echo $prefix;
|
||||
|
||||
$input = fgets(fopen('php://stdin', 'rb'));
|
||||
|
||||
if ($input === false) {
|
||||
$input = '';
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* While the library is intended for use on CLI commands,
|
||||
* commands can be called from controllers and elsewhere
|
||||
* so we need a way to allow them to still work.
|
||||
*
|
||||
* For now, just echo the content, but look into a better
|
||||
* solution down the road.
|
||||
*
|
||||
* @param resource $handle
|
||||
*/
|
||||
public function fwrite($handle, string $string): void
|
||||
{
|
||||
if (! is_cli()) {
|
||||
echo $string;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite($handle, $string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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\Cache;
|
||||
|
||||
use CodeIgniter\Cache\Exceptions\CacheException;
|
||||
use CodeIgniter\Exceptions\CriticalError;
|
||||
use CodeIgniter\Test\Mock\MockCache;
|
||||
use Config\Cache;
|
||||
|
||||
/**
|
||||
* A factory for loading the desired
|
||||
*
|
||||
* @see \CodeIgniter\Cache\CacheFactoryTest
|
||||
*/
|
||||
class CacheFactory
|
||||
{
|
||||
/**
|
||||
* The class to use when mocking
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $mockClass = MockCache::class;
|
||||
|
||||
/**
|
||||
* The service to inject the mock as
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $mockServiceName = 'cache';
|
||||
|
||||
/**
|
||||
* Attempts to create the desired cache handler, based upon the
|
||||
*
|
||||
* @param non-empty-string|null $handler
|
||||
* @param non-empty-string|null $backup
|
||||
*
|
||||
* @return CacheInterface
|
||||
*/
|
||||
public static function getHandler(Cache $config, ?string $handler = null, ?string $backup = null)
|
||||
{
|
||||
if (! isset($config->validHandlers) || $config->validHandlers === []) {
|
||||
throw CacheException::forInvalidHandlers();
|
||||
}
|
||||
|
||||
if (! isset($config->handler) || ! isset($config->backupHandler)) {
|
||||
throw CacheException::forNoBackup();
|
||||
}
|
||||
|
||||
$handler ??= $config->handler;
|
||||
$backup ??= $config->backupHandler;
|
||||
|
||||
if (! array_key_exists($handler, $config->validHandlers) || ! array_key_exists($backup, $config->validHandlers)) {
|
||||
throw CacheException::forHandlerNotFound();
|
||||
}
|
||||
|
||||
$adapter = new $config->validHandlers[$handler]($config);
|
||||
|
||||
if (! $adapter->isSupported()) {
|
||||
$adapter = new $config->validHandlers[$backup]($config);
|
||||
|
||||
if (! $adapter->isSupported()) {
|
||||
// Fall back to the dummy adapter.
|
||||
$adapter = new $config->validHandlers['dummy']();
|
||||
}
|
||||
}
|
||||
|
||||
// If $adapter->initialization throws a CriticalError exception, we will attempt to
|
||||
// use the $backup handler, if that also fails, we resort to the dummy handler.
|
||||
try {
|
||||
$adapter->initialize();
|
||||
} catch (CriticalError $e) {
|
||||
log_message('critical', $e . ' Resorting to using ' . $backup . ' handler.');
|
||||
|
||||
// get the next best cache handler (or dummy if the $backup also fails)
|
||||
$adapter = self::getHandler($config, $backup, 'dummy');
|
||||
}
|
||||
|
||||
return $adapter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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\Cache;
|
||||
|
||||
/**
|
||||
* Cache interface
|
||||
*/
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Takes care of any handler-specific setup that must be done.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Attempts to fetch an item from the cache store.
|
||||
*
|
||||
* @param string $key Cache item name
|
||||
*
|
||||
* @return array|bool|float|int|object|string|null
|
||||
*/
|
||||
public function get(string $key);
|
||||
|
||||
/**
|
||||
* Saves an item to the cache store.
|
||||
*
|
||||
* @param string $key Cache item name
|
||||
* @param array|bool|float|int|object|string|null $value The data to save
|
||||
* @param int $ttl Time To Live, in seconds (default 60)
|
||||
*
|
||||
* @return bool Success or failure
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60);
|
||||
|
||||
/**
|
||||
* Deletes a specific item from the cache store.
|
||||
*
|
||||
* @param string $key Cache item name
|
||||
*
|
||||
* @return bool Success or failure
|
||||
*/
|
||||
public function delete(string $key);
|
||||
|
||||
/**
|
||||
* Performs atomic incrementation of a raw stored value.
|
||||
*
|
||||
* @param string $key Cache ID
|
||||
* @param int $offset Step/value to increase by
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1);
|
||||
|
||||
/**
|
||||
* Performs atomic decrementation of a raw stored value.
|
||||
*
|
||||
* @param string $key Cache ID
|
||||
* @param int $offset Step/value to increase by
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1);
|
||||
|
||||
/**
|
||||
* Will delete all items in the entire cache.
|
||||
*
|
||||
* @return bool Success or failure
|
||||
*/
|
||||
public function clean();
|
||||
|
||||
/**
|
||||
* Returns information on the entire cache.
|
||||
*
|
||||
* The information returned and the structure of the data
|
||||
* varies depending on the handler.
|
||||
*
|
||||
* @return array|false|object|null
|
||||
*/
|
||||
public function getCacheInfo();
|
||||
|
||||
/**
|
||||
* Returns detailed information about the specific item in the cache.
|
||||
*
|
||||
* @param string $key Cache item name.
|
||||
*
|
||||
* @return array|false|null
|
||||
* Returns null if the item does not exist, otherwise array<string, mixed>
|
||||
* with at least the 'expire' key for absolute epoch expiry (or null).
|
||||
* Some handlers may return false when an item does not exist, which is deprecated.
|
||||
*/
|
||||
public function getMetaData(string $key);
|
||||
|
||||
/**
|
||||
* Determines if the driver is supported on this system.
|
||||
*/
|
||||
public function isSupported(): bool;
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?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\Cache\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\DebugTraceableTrait;
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* CacheException
|
||||
*/
|
||||
class CacheException extends RuntimeException
|
||||
{
|
||||
use DebugTraceableTrait;
|
||||
|
||||
/**
|
||||
* Thrown when handler has no permission to write cache.
|
||||
*
|
||||
* @return CacheException
|
||||
*/
|
||||
public static function forUnableToWrite(string $path)
|
||||
{
|
||||
return new static(lang('Cache.unableToWrite', [$path]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when an unrecognized handler is used.
|
||||
*
|
||||
* @return CacheException
|
||||
*/
|
||||
public static function forInvalidHandlers()
|
||||
{
|
||||
return new static(lang('Cache.invalidHandlers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when no backup handler is setup in config.
|
||||
*
|
||||
* @return CacheException
|
||||
*/
|
||||
public static function forNoBackup()
|
||||
{
|
||||
return new static(lang('Cache.noBackup'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when specified handler was not found.
|
||||
*
|
||||
* @return CacheException
|
||||
*/
|
||||
public static function forHandlerNotFound()
|
||||
{
|
||||
return new static(lang('Cache.handlerNotFound'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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\Cache;
|
||||
|
||||
use CodeIgniter\Cache\FactoriesCache\FileVarExportHandler;
|
||||
use CodeIgniter\Config\Factories;
|
||||
|
||||
final class FactoriesCache
|
||||
{
|
||||
/**
|
||||
* @var CacheInterface|FileVarExportHandler
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param CacheInterface|FileVarExportHandler|null $cache
|
||||
*/
|
||||
public function __construct($cache = null)
|
||||
{
|
||||
$this->cache = $cache ?? new FileVarExportHandler();
|
||||
}
|
||||
|
||||
public function save(string $component): void
|
||||
{
|
||||
if (! Factories::isUpdated($component)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = Factories::getComponentInstances($component);
|
||||
|
||||
$this->cache->save($this->getCacheKey($component), $data, 3600 * 24);
|
||||
}
|
||||
|
||||
private function getCacheKey(string $component): string
|
||||
{
|
||||
return 'FactoriesCache_' . $component;
|
||||
}
|
||||
|
||||
public function load(string $component): bool
|
||||
{
|
||||
$key = $this->getCacheKey($component);
|
||||
|
||||
if (! $data = $this->cache->get($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Factories::setComponentInstances($component, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete(string $component): void
|
||||
{
|
||||
$this->cache->delete($this->getCacheKey($component));
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?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\Cache\FactoriesCache;
|
||||
|
||||
final class FileVarExportHandler
|
||||
{
|
||||
private string $path = WRITEPATH . 'cache';
|
||||
|
||||
/**
|
||||
* @param array|bool|float|int|object|string|null $val
|
||||
*/
|
||||
public function save(string $key, $val): void
|
||||
{
|
||||
$val = var_export($val, true);
|
||||
|
||||
// Write to temp file first to ensure atomicity
|
||||
$tmp = $this->path . "/{$key}." . uniqid('', true) . '.tmp';
|
||||
file_put_contents($tmp, '<?php return ' . $val . ';', LOCK_EX);
|
||||
|
||||
rename($tmp, $this->path . "/{$key}");
|
||||
}
|
||||
|
||||
public function delete(string $key): void
|
||||
{
|
||||
@unlink($this->path . "/{$key}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|bool|float|int|object|string|null
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
return @include $this->path . "/{$key}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use Closure;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Exceptions\BadMethodCallException;
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use Config\Cache;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Base class for cache handling
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\BaseHandlerTest
|
||||
*/
|
||||
abstract class BaseHandler implements CacheInterface
|
||||
{
|
||||
/**
|
||||
* Reserved characters that cannot be used in a key or tag. May be overridden by the config.
|
||||
* From https://github.com/symfony/cache-contracts/blob/c0446463729b89dd4fa62e9aeecc80287323615d/ItemInterface.php#L43
|
||||
*
|
||||
* @deprecated in favor of the Cache config
|
||||
*/
|
||||
public const RESERVED_CHARACTERS = '{}()/\@:';
|
||||
|
||||
/**
|
||||
* Maximum key length.
|
||||
*/
|
||||
public const MAX_KEY_LENGTH = PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* Prefix to apply to cache keys.
|
||||
* May not be used by all handlers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* Validates a cache key according to PSR-6.
|
||||
* Keys that exceed MAX_KEY_LENGTH are hashed.
|
||||
* From https://github.com/symfony/cache/blob/7b024c6726af21fd4984ac8d1eae2b9f3d90de88/CacheItem.php#L158
|
||||
*
|
||||
* @param string $key The key to validate
|
||||
* @param string $prefix Optional prefix to include in length calculations
|
||||
*
|
||||
* @throws InvalidArgumentException When $key is not valid
|
||||
*/
|
||||
public static function validateKey($key, $prefix = ''): string
|
||||
{
|
||||
if (! is_string($key)) {
|
||||
throw new InvalidArgumentException('Cache key must be a string');
|
||||
}
|
||||
if ($key === '') {
|
||||
throw new InvalidArgumentException('Cache key cannot be empty.');
|
||||
}
|
||||
|
||||
$reserved = config(Cache::class)->reservedCharacters ?? self::RESERVED_CHARACTERS;
|
||||
if ($reserved !== '' && strpbrk($key, $reserved) !== false) {
|
||||
throw new InvalidArgumentException('Cache key contains reserved characters ' . $reserved);
|
||||
}
|
||||
|
||||
// If the key with prefix exceeds the length then return the hashed version
|
||||
return strlen($prefix . $key) > static::MAX_KEY_LENGTH ? $prefix . md5($key) : $prefix . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item from the cache, or execute the given Closure and store the result.
|
||||
*
|
||||
* @param string $key Cache item name
|
||||
* @param int $ttl Time to live
|
||||
* @param Closure(): mixed $callback Callback return value
|
||||
*
|
||||
* @return array|bool|float|int|object|string|null
|
||||
*/
|
||||
public function remember(string $key, int $ttl, Closure $callback)
|
||||
{
|
||||
$value = $this->get($key);
|
||||
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->save($key, $value = $callback(), $ttl);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes items from the cache store matching a given pattern.
|
||||
*
|
||||
* @param string $pattern Cache items glob-style pattern
|
||||
*
|
||||
* @return int|never
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
throw new BadMethodCallException('The deleteMatching method is not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Dummy cache handler
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\DummyHandlerTest
|
||||
*/
|
||||
class DummyHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function remember(string $key, int $ttl, Closure $callback)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use CodeIgniter\Cache\Exceptions\CacheException;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* File system cache handler
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\FileHandlerTest
|
||||
*/
|
||||
class FileHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* Maximum key length.
|
||||
*/
|
||||
public const MAX_KEY_LENGTH = 255;
|
||||
|
||||
/**
|
||||
* Where to store cached files on the disk.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Mode for the stored files.
|
||||
* Must be chmod-safe (octal).
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.chmod.php
|
||||
*/
|
||||
protected $mode;
|
||||
|
||||
/**
|
||||
* Note: Use `CacheFactory::getHandler()` to instantiate.
|
||||
*
|
||||
* @throws CacheException
|
||||
*/
|
||||
public function __construct(Cache $config)
|
||||
{
|
||||
$this->path = ! empty($config->file['storePath']) ? $config->file['storePath'] : WRITEPATH . 'cache';
|
||||
$this->path = rtrim($this->path, '/') . '/';
|
||||
|
||||
if (! is_really_writable($this->path)) {
|
||||
throw CacheException::forUnableToWrite($this->path);
|
||||
}
|
||||
|
||||
$this->mode = $config->file['mode'] ?? 0640;
|
||||
$this->prefix = $config->prefix;
|
||||
|
||||
helper('filesystem');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
$data = $this->getItem($key);
|
||||
|
||||
return is_array($data) ? $data['data'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
$contents = [
|
||||
'time' => Time::now()->getTimestamp(),
|
||||
'ttl' => $ttl,
|
||||
'data' => $value,
|
||||
];
|
||||
|
||||
if (write_file($this->path . $key, serialize($contents))) {
|
||||
try {
|
||||
chmod($this->path . $key, $this->mode);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Throwable $e) {
|
||||
log_message('debug', 'Failed to set mode on cache file: ' . $e);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return is_file($this->path . $key) && unlink($this->path . $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
$deleted = 0;
|
||||
|
||||
foreach (glob($this->path . $pattern, GLOB_NOSORT) as $filename) {
|
||||
if (is_file($filename) && @unlink($filename)) {
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
$prefixedKey = static::validateKey($key, $this->prefix);
|
||||
$tmp = $this->getItem($prefixedKey);
|
||||
|
||||
if ($tmp === false) {
|
||||
$tmp = ['data' => 0, 'ttl' => 60];
|
||||
}
|
||||
|
||||
['data' => $value, 'ttl' => $ttl] = $tmp;
|
||||
|
||||
if (! is_int($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value += $offset;
|
||||
|
||||
return $this->save($key, $value, $ttl) ? $value : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
return $this->increment($key, -$offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return delete_files($this->path, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return get_dir_file_info($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
if (false === $data = $this->getItem($key)) {
|
||||
return false; // @TODO This will return null in a future release
|
||||
}
|
||||
|
||||
return [
|
||||
'expire' => $data['ttl'] > 0 ? $data['time'] + $data['ttl'] : null,
|
||||
'mtime' => filemtime($this->path . $key),
|
||||
'data' => $data['data'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return is_writable($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the heavy lifting of actually retrieving the file and
|
||||
* verifying it's age.
|
||||
*
|
||||
* @return array{data: mixed, ttl: int, time: int}|false
|
||||
*/
|
||||
protected function getItem(string $filename)
|
||||
{
|
||||
if (! is_file($this->path . $filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = @unserialize(file_get_contents($this->path . $filename));
|
||||
|
||||
if (! is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($data['ttl']) || ! is_int($data['ttl'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($data['time']) || ! is_int($data['time'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data['ttl'] > 0 && Time::now()->getTimestamp() > $data['time'] + $data['ttl']) {
|
||||
@unlink($this->path . $filename);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to disk, or returns false if not successful.
|
||||
*
|
||||
* @deprecated 4.6.1 Use `write_file()` instead.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $data
|
||||
* @param string $mode
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function writeFile($path, $data, $mode = 'wb')
|
||||
{
|
||||
if (($fp = @fopen($path, $mode)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
flock($fp, LOCK_EX);
|
||||
|
||||
$result = 0;
|
||||
|
||||
for ($written = 0, $length = strlen($data); $written < $length; $written += $result) {
|
||||
if (($result = fwrite($fp, substr($data, $written))) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
return is_int($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all files contained in the supplied directory path.
|
||||
* Files must be writable or owned by the system in order to be deleted.
|
||||
* If the second parameter is set to TRUE, any directories contained
|
||||
* within the supplied base directory will be nuked as well.
|
||||
*
|
||||
* @deprecated 4.6.1 Use `delete_files()` instead.
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param bool $delDir Whether to delete any directories found in the path
|
||||
* @param bool $htdocs Whether to skip deleting .htaccess and index page files
|
||||
* @param int $_level Current directory depth level (default: 0; internal use only)
|
||||
*/
|
||||
protected function deleteFiles(string $path, bool $delDir = false, bool $htdocs = false, int $_level = 0): bool
|
||||
{
|
||||
// Trim the trailing slash
|
||||
$path = rtrim($path, '/\\');
|
||||
|
||||
if (! $currentDir = @opendir($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (false !== ($filename = @readdir($currentDir))) {
|
||||
if ($filename !== '.' && $filename !== '..') {
|
||||
if (is_dir($path . DIRECTORY_SEPARATOR . $filename) && $filename[0] !== '.') {
|
||||
$this->deleteFiles($path . DIRECTORY_SEPARATOR . $filename, $delDir, $htdocs, $_level + 1);
|
||||
} elseif (! $htdocs || preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename) !== 1) {
|
||||
@unlink($path . DIRECTORY_SEPARATOR . $filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($currentDir);
|
||||
|
||||
return ($delDir && $_level > 0) ? @rmdir($path) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified directory and builds an array containing the filenames,
|
||||
* filesize, dates, and permissions
|
||||
*
|
||||
* Any sub-folders contained within the specified path are read as well.
|
||||
*
|
||||
* @deprecated 4.6.1 Use `get_dir_file_info()` instead.
|
||||
*
|
||||
* @param string $sourceDir Path to source
|
||||
* @param bool $topLevelOnly Look only at the top level directory specified?
|
||||
* @param bool $_recursion Internal variable to determine recursion status - do not use in calls
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function getDirFileInfo(string $sourceDir, bool $topLevelOnly = true, bool $_recursion = false)
|
||||
{
|
||||
static $_filedata = [];
|
||||
$relativePath = $sourceDir;
|
||||
|
||||
if ($fp = @opendir($sourceDir)) {
|
||||
// reset the array and make sure $sourceDir has a trailing slash on the initial call
|
||||
if ($_recursion === false) {
|
||||
$_filedata = [];
|
||||
$sourceDir = rtrim(realpath($sourceDir) ?: $sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
// Used to be foreach (scandir($sourceDir, 1) as $file), but scandir() is simply not as fast
|
||||
while (false !== ($file = readdir($fp))) {
|
||||
if (is_dir($sourceDir . $file) && $file[0] !== '.' && $topLevelOnly === false) {
|
||||
$this->getDirFileInfo($sourceDir . $file . DIRECTORY_SEPARATOR, $topLevelOnly, true);
|
||||
} elseif (! is_dir($sourceDir . $file) && $file[0] !== '.') {
|
||||
$_filedata[$file] = $this->getFileInfo($sourceDir . $file);
|
||||
$_filedata[$file]['relative_path'] = $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
closedir($fp);
|
||||
|
||||
return $_filedata;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file and path, returns the name, path, size, date modified
|
||||
* Second parameter allows you to explicitly declare what information you want returned
|
||||
* Options are: name, server_path, size, date, readable, writable, executable, fileperms
|
||||
* Returns FALSE if the file cannot be found.
|
||||
*
|
||||
* @deprecated 4.6.1 Use `get_file_info()` instead.
|
||||
*
|
||||
* @param string $file Path to file
|
||||
* @param array|string $returnedValues Array or comma separated string of information returned
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function getFileInfo(string $file, $returnedValues = ['name', 'server_path', 'size', 'date'])
|
||||
{
|
||||
if (! is_file($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_string($returnedValues)) {
|
||||
$returnedValues = explode(',', $returnedValues);
|
||||
}
|
||||
|
||||
$fileInfo = [];
|
||||
|
||||
foreach ($returnedValues as $key) {
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
$fileInfo['name'] = basename($file);
|
||||
break;
|
||||
|
||||
case 'server_path':
|
||||
$fileInfo['server_path'] = $file;
|
||||
break;
|
||||
|
||||
case 'size':
|
||||
$fileInfo['size'] = filesize($file);
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
$fileInfo['date'] = filemtime($file);
|
||||
break;
|
||||
|
||||
case 'readable':
|
||||
$fileInfo['readable'] = is_readable($file);
|
||||
break;
|
||||
|
||||
case 'writable':
|
||||
$fileInfo['writable'] = is_writable($file);
|
||||
break;
|
||||
|
||||
case 'executable':
|
||||
$fileInfo['executable'] = is_executable($file);
|
||||
break;
|
||||
|
||||
case 'fileperms':
|
||||
$fileInfo['fileperms'] = fileperms($file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $fileInfo;
|
||||
}
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use CodeIgniter\Exceptions\BadMethodCallException;
|
||||
use CodeIgniter\Exceptions\CriticalError;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
use Exception;
|
||||
use Memcache;
|
||||
use Memcached;
|
||||
|
||||
/**
|
||||
* Mamcached cache handler
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\MemcachedHandlerTest
|
||||
*/
|
||||
class MemcachedHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* The memcached object
|
||||
*
|
||||
* @var Memcache|Memcached
|
||||
*/
|
||||
protected $memcached;
|
||||
|
||||
/**
|
||||
* Memcached Configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'weight' => 1,
|
||||
'raw' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Note: Use `CacheFactory::getHandler()` to instantiate.
|
||||
*/
|
||||
public function __construct(Cache $config)
|
||||
{
|
||||
$this->prefix = $config->prefix;
|
||||
|
||||
$this->config = array_merge($this->config, $config->memcached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to Memcache(d) if present.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->memcached instanceof Memcached) {
|
||||
$this->memcached->quit();
|
||||
} elseif ($this->memcached instanceof Memcache) {
|
||||
$this->memcached->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
try {
|
||||
if (class_exists(Memcached::class)) {
|
||||
// Create new instance of Memcached
|
||||
$this->memcached = new Memcached();
|
||||
if ($this->config['raw']) {
|
||||
$this->memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
|
||||
}
|
||||
|
||||
// Add server
|
||||
$this->memcached->addServer(
|
||||
$this->config['host'],
|
||||
$this->config['port'],
|
||||
$this->config['weight'],
|
||||
);
|
||||
|
||||
// attempt to get status of servers
|
||||
$stats = $this->memcached->getStats();
|
||||
|
||||
// $stats should be an associate array with a key in the format of host:port.
|
||||
// If it doesn't have the key, we know the server is not working as expected.
|
||||
if (! isset($stats[$this->config['host'] . ':' . $this->config['port']])) {
|
||||
throw new CriticalError('Cache: Memcached connection failed.');
|
||||
}
|
||||
} elseif (class_exists(Memcache::class)) {
|
||||
// Create new instance of Memcache
|
||||
$this->memcached = new Memcache();
|
||||
|
||||
// Check if we can connect to the server
|
||||
$canConnect = $this->memcached->connect(
|
||||
$this->config['host'],
|
||||
$this->config['port'],
|
||||
);
|
||||
|
||||
// If we can't connect, throw a CriticalError exception
|
||||
if ($canConnect === false) {
|
||||
throw new CriticalError('Cache: Memcache connection failed.');
|
||||
}
|
||||
|
||||
// Add server, third parameter is persistence and defaults to TRUE.
|
||||
$this->memcached->addServer(
|
||||
$this->config['host'],
|
||||
$this->config['port'],
|
||||
true,
|
||||
$this->config['weight'],
|
||||
);
|
||||
} else {
|
||||
throw new CriticalError('Cache: Not support Memcache(d) extension.');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new CriticalError('Cache: Memcache(d) connection refused (' . $e->getMessage() . ').');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
$data = [];
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
if ($this->memcached instanceof Memcached) {
|
||||
$data = $this->memcached->get($key);
|
||||
|
||||
// check for unmatched key
|
||||
if ($this->memcached->getResultCode() === Memcached::RES_NOTFOUND) {
|
||||
return null;
|
||||
}
|
||||
} elseif ($this->memcached instanceof Memcache) {
|
||||
$flags = false;
|
||||
$data = $this->memcached->get($key, $flags);
|
||||
|
||||
// check for unmatched key (i.e. $flags is untouched)
|
||||
if ($flags === false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return is_array($data) ? $data[0] : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
if (! $this->config['raw']) {
|
||||
$value = [
|
||||
$value,
|
||||
Time::now()->getTimestamp(),
|
||||
$ttl,
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->memcached instanceof Memcached) {
|
||||
return $this->memcached->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
if ($this->memcached instanceof Memcache) {
|
||||
return $this->memcached->set($key, $value, 0, $ttl);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return $this->memcached->delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
throw new BadMethodCallException('The deleteMatching method is not implemented for Memcached. You must select File, Redis or Predis handlers to use it.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
if (! $this->config['raw']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return $this->memcached->increment($key, $offset, $offset, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
if (! $this->config['raw']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
// FIXME: third parameter isn't other handler actions.
|
||||
|
||||
return $this->memcached->decrement($key, $offset, $offset, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return $this->memcached->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return $this->memcached->getStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
$stored = $this->memcached->get($key);
|
||||
|
||||
// if not an array, don't try to count for PHP7.2
|
||||
if (! is_array($stored) || count($stored) !== 3) {
|
||||
return false; // @TODO This will return null in a future release
|
||||
}
|
||||
|
||||
[$data, $time, $limit] = $stored;
|
||||
|
||||
return [
|
||||
'expire' => $limit > 0 ? $time + $limit : null,
|
||||
'mtime' => $time,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return extension_loaded('memcached') || extension_loaded('memcache');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use CodeIgniter\Exceptions\CriticalError;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
use Exception;
|
||||
use Predis\Client;
|
||||
use Predis\Collection\Iterator\Keyspace;
|
||||
use Predis\Response\Status;
|
||||
|
||||
/**
|
||||
* Predis cache handler
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\PredisHandlerTest
|
||||
*/
|
||||
class PredisHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* Default config
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* Predis connection
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* Note: Use `CacheFactory::getHandler()` to instantiate.
|
||||
*/
|
||||
public function __construct(Cache $config)
|
||||
{
|
||||
$this->prefix = $config->prefix;
|
||||
|
||||
if (isset($config->redis)) {
|
||||
$this->config = array_merge($this->config, $config->redis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
try {
|
||||
$this->redis = new Client($this->config, ['prefix' => $this->prefix]);
|
||||
$this->redis->time();
|
||||
} catch (Exception $e) {
|
||||
throw new CriticalError('Cache: Predis connection refused (' . $e->getMessage() . ').');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
$data = array_combine(
|
||||
['__ci_type', '__ci_value'],
|
||||
$this->redis->hmget($key, ['__ci_type', '__ci_value']),
|
||||
);
|
||||
|
||||
if (! isset($data['__ci_type'], $data['__ci_value']) || $data['__ci_value'] === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($data['__ci_type']) {
|
||||
'array', 'object' => unserialize($data['__ci_value']),
|
||||
// Yes, 'double' is returned and NOT 'float'
|
||||
'boolean', 'integer', 'double', 'string', 'NULL' => settype($data['__ci_value'], $data['__ci_type']) ? $data['__ci_value'] : null,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
switch ($dataType = gettype($value)) {
|
||||
case 'array':
|
||||
case 'object':
|
||||
$value = serialize($value);
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
case 'double': // Yes, 'double' is returned and NOT 'float'
|
||||
case 'string':
|
||||
case 'NULL':
|
||||
break;
|
||||
|
||||
case 'resource':
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->redis->hmset($key, ['__ci_type' => $dataType, '__ci_value' => $value]) instanceof Status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ttl !== 0) {
|
||||
$this->redis->expireat($key, Time::now()->getTimestamp() + $ttl);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
return $this->redis->del($key) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
$matchedKeys = [];
|
||||
|
||||
foreach (new Keyspace($this->redis, $pattern) as $key) {
|
||||
$matchedKeys[] = $key;
|
||||
}
|
||||
|
||||
return $this->redis->del($matchedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
return $this->redis->hincrby($key, 'data', $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
return $this->redis->hincrby($key, 'data', -$offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return $this->redis->flushdb()->getPayload() === 'OK';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return $this->redis->info();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
$key = static::validateKey($key);
|
||||
|
||||
$data = array_combine(['__ci_value'], $this->redis->hmget($key, ['__ci_value']));
|
||||
|
||||
if (isset($data['__ci_value']) && $data['__ci_value'] !== false) {
|
||||
$time = Time::now()->getTimestamp();
|
||||
$ttl = $this->redis->ttl($key);
|
||||
|
||||
return [
|
||||
'expire' => $ttl > 0 ? $time + $ttl : null,
|
||||
'mtime' => $time,
|
||||
'data' => $data['__ci_value'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return class_exists(Client::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use CodeIgniter\Exceptions\CriticalError;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
|
||||
/**
|
||||
* Redis cache handler
|
||||
*
|
||||
* @see \CodeIgniter\Cache\Handlers\RedisHandlerTest
|
||||
*/
|
||||
class RedisHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* Default config
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'database' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* Redis connection
|
||||
*
|
||||
* @var Redis|null
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* Note: Use `CacheFactory::getHandler()` to instantiate.
|
||||
*/
|
||||
public function __construct(Cache $config)
|
||||
{
|
||||
$this->prefix = $config->prefix;
|
||||
|
||||
$this->config = array_merge($this->config, $config->redis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to Redis if present.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->redis)) {
|
||||
$this->redis->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$config = $this->config;
|
||||
|
||||
$this->redis = new Redis();
|
||||
|
||||
try {
|
||||
// Note:: If Redis is your primary cache choice, and it is "offline", every page load will end up been delayed by the timeout duration.
|
||||
// I feel like some sort of temporary flag should be set, to indicate that we think Redis is "offline", allowing us to bypass the timeout for a set period of time.
|
||||
|
||||
if (! $this->redis->connect($config['host'], ($config['host'][0] === '/' ? 0 : $config['port']), $config['timeout'])) {
|
||||
// Note:: I'm unsure if log_message() is necessary, however I'm not 100% comfortable removing it.
|
||||
log_message('error', 'Cache: Redis connection failed. Check your configuration.');
|
||||
|
||||
throw new CriticalError('Cache: Redis connection failed. Check your configuration.');
|
||||
}
|
||||
|
||||
if (isset($config['password']) && ! $this->redis->auth($config['password'])) {
|
||||
log_message('error', 'Cache: Redis authentication failed.');
|
||||
|
||||
throw new CriticalError('Cache: Redis authentication failed.');
|
||||
}
|
||||
|
||||
if (isset($config['database']) && ! $this->redis->select($config['database'])) {
|
||||
log_message('error', 'Cache: Redis select database failed.');
|
||||
|
||||
throw new CriticalError('Cache: Redis select database failed.');
|
||||
}
|
||||
} catch (RedisException $e) {
|
||||
throw new CriticalError('Cache: RedisException occurred with message (' . $e->getMessage() . ').');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
$data = $this->redis->hMget($key, ['__ci_type', '__ci_value']);
|
||||
|
||||
if (! isset($data['__ci_type'], $data['__ci_value']) || $data['__ci_value'] === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($data['__ci_type']) {
|
||||
'array', 'object' => unserialize($data['__ci_value']),
|
||||
// Yes, 'double' is returned and NOT 'float'
|
||||
'boolean', 'integer', 'double', 'string', 'NULL' => settype($data['__ci_value'], $data['__ci_type']) ? $data['__ci_value'] : null,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
switch ($dataType = gettype($value)) {
|
||||
case 'array':
|
||||
case 'object':
|
||||
$value = serialize($value);
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
case 'double': // Yes, 'double' is returned and NOT 'float'
|
||||
case 'string':
|
||||
case 'NULL':
|
||||
break;
|
||||
|
||||
case 'resource':
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->redis->hMset($key, ['__ci_type' => $dataType, '__ci_value' => $value])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ttl !== 0) {
|
||||
$this->redis->expireAt($key, Time::now()->getTimestamp() + $ttl);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return $this->redis->del($key) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
/** @var list<string> $matchedKeys */
|
||||
$matchedKeys = [];
|
||||
$pattern = static::validateKey($pattern, $this->prefix);
|
||||
$iterator = null;
|
||||
|
||||
do {
|
||||
/** @var false|list<string> $keys */
|
||||
$keys = $this->redis->scan($iterator, $pattern);
|
||||
|
||||
if (is_array($keys)) {
|
||||
$matchedKeys = [...$matchedKeys, ...$keys];
|
||||
}
|
||||
} while ($iterator > 0);
|
||||
|
||||
return $this->redis->del($matchedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return $this->redis->hIncrBy($key, '__ci_value', $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
return $this->increment($key, -$offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return $this->redis->flushDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return $this->redis->info();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
$value = $this->get($key);
|
||||
|
||||
if ($value !== null) {
|
||||
$time = Time::now()->getTimestamp();
|
||||
$ttl = $this->redis->ttl(static::validateKey($key, $this->prefix));
|
||||
assert(is_int($ttl));
|
||||
|
||||
return [
|
||||
'expire' => $ttl > 0 ? $time + $ttl : null,
|
||||
'mtime' => $time,
|
||||
'data' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return extension_loaded('redis');
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
<?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\Cache\Handlers;
|
||||
|
||||
use CodeIgniter\Exceptions\BadMethodCallException;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
|
||||
/**
|
||||
* Cache handler for WinCache from Microsoft & IIS.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WincacheHandler extends BaseHandler
|
||||
{
|
||||
/**
|
||||
* Note: Use `CacheFactory::getHandler()` to instantiate.
|
||||
*/
|
||||
public function __construct(Cache $config)
|
||||
{
|
||||
$this->prefix = $config->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
$success = false;
|
||||
|
||||
$data = wincache_ucache_get($key, $success);
|
||||
|
||||
// Success returned by reference from wincache_ucache_get()
|
||||
return $success ? $data : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(string $key, $value, int $ttl = 60)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return wincache_ucache_set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return wincache_ucache_delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function deleteMatching(string $pattern)
|
||||
{
|
||||
throw new BadMethodCallException('The deleteMatching method is not implemented for Wincache. You must select File, Redis or Predis handlers to use it.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function increment(string $key, int $offset = 1)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return wincache_ucache_inc($key, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function decrement(string $key, int $offset = 1)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
return wincache_ucache_dec($key, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
return wincache_ucache_clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCacheInfo()
|
||||
{
|
||||
return wincache_ucache_info(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMetaData(string $key)
|
||||
{
|
||||
$key = static::validateKey($key, $this->prefix);
|
||||
|
||||
if ($stored = wincache_ucache_info(false, $key)) {
|
||||
$age = $stored['ucache_entries'][1]['age_seconds'];
|
||||
$ttl = $stored['ucache_entries'][1]['ttl_seconds'];
|
||||
$hitcount = $stored['ucache_entries'][1]['hitcount'];
|
||||
|
||||
return [
|
||||
'expire' => $ttl > 0 ? Time::now()->getTimestamp() + $ttl : null,
|
||||
'hitcount' => $hitcount,
|
||||
'age' => $age,
|
||||
'ttl' => $ttl,
|
||||
];
|
||||
}
|
||||
|
||||
return false; // @TODO This will return null in a future release
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isSupported(): bool
|
||||
{
|
||||
return extension_loaded('wincache') && ini_get('wincache.ucenabled');
|
||||
}
|
||||
}
|
||||
@@ -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\Cache;
|
||||
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
use CodeIgniter\HTTP\CLIRequest;
|
||||
use CodeIgniter\HTTP\Header;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Cache as CacheConfig;
|
||||
|
||||
/**
|
||||
* Web Page Caching
|
||||
*
|
||||
* @see \CodeIgniter\Cache\ResponseCacheTest
|
||||
*/
|
||||
final class ResponseCache
|
||||
{
|
||||
/**
|
||||
* Whether to take the URL query string into consideration when generating
|
||||
* output cache files. Valid options are:
|
||||
*
|
||||
* false = Disabled
|
||||
* true = Enabled, take all query parameters into account.
|
||||
* Please be aware that this may result in numerous cache
|
||||
* files generated for the same page over and over again.
|
||||
* array('q') = Enabled, but only take into account the specified list
|
||||
* of query parameters.
|
||||
*
|
||||
* @var bool|list<string>
|
||||
*/
|
||||
private $cacheQueryString = false;
|
||||
|
||||
/**
|
||||
* Cache time to live.
|
||||
*
|
||||
* @var int seconds
|
||||
*/
|
||||
private int $ttl = 0;
|
||||
|
||||
public function __construct(CacheConfig $config, private readonly CacheInterface $cache)
|
||||
{
|
||||
$this->cacheQueryString = $config->cacheQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setTtl(int $ttl)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the cache key to use from the current request.
|
||||
*
|
||||
* @param CLIRequest|IncomingRequest $request
|
||||
*
|
||||
* @internal for testing purposes only
|
||||
*/
|
||||
public function generateCacheKey($request): string
|
||||
{
|
||||
if ($request instanceof CLIRequest) {
|
||||
return md5($request->getPath());
|
||||
}
|
||||
|
||||
$uri = clone $request->getUri();
|
||||
|
||||
$query = $this->cacheQueryString
|
||||
? $uri->getQuery(is_array($this->cacheQueryString) ? ['only' => $this->cacheQueryString] : [])
|
||||
: '';
|
||||
|
||||
return md5($request->getMethod() . ':' . $uri->setFragment('')->setQuery($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the response.
|
||||
*
|
||||
* @param CLIRequest|IncomingRequest $request
|
||||
*/
|
||||
public function make($request, ResponseInterface $response): bool
|
||||
{
|
||||
if ($this->ttl === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
|
||||
foreach ($response->headers() as $name => $value) {
|
||||
if ($value instanceof Header) {
|
||||
$headers[$name] = $value->getValueLine();
|
||||
} else {
|
||||
foreach ($value as $header) {
|
||||
$headers[$name][] = $header->getValueLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache->save(
|
||||
$this->generateCacheKey($request),
|
||||
serialize(['headers' => $headers, 'output' => $response->getBody()]),
|
||||
$this->ttl,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached response for the request.
|
||||
*
|
||||
* @param CLIRequest|IncomingRequest $request
|
||||
*/
|
||||
public function get($request, ResponseInterface $response): ?ResponseInterface
|
||||
{
|
||||
if ($cachedResponse = $this->cache->get($this->generateCacheKey($request))) {
|
||||
$cachedResponse = unserialize($cachedResponse);
|
||||
|
||||
if (
|
||||
! is_array($cachedResponse)
|
||||
|| ! isset($cachedResponse['output'])
|
||||
|| ! isset($cachedResponse['headers'])
|
||||
) {
|
||||
throw new RuntimeException('Error unserializing page cache');
|
||||
}
|
||||
|
||||
$headers = $cachedResponse['headers'];
|
||||
$output = $cachedResponse['output'];
|
||||
|
||||
// Clear all default headers
|
||||
foreach (array_keys($response->headers()) as $key) {
|
||||
$response->removeHeader($key);
|
||||
}
|
||||
|
||||
// Set cached headers
|
||||
foreach ($headers as $name => $value) {
|
||||
$response->setHeader($name, $value);
|
||||
}
|
||||
|
||||
$response->setBody($output);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
<?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\Cache;
|
||||
|
||||
use CodeIgniter\Cache\CacheFactory;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use Config\Cache;
|
||||
|
||||
/**
|
||||
* Clears current cache.
|
||||
*/
|
||||
class ClearCache extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Command grouping.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Cache';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cache:clear';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clears the current system caches.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'cache:clear [<driver>]';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'driver' => 'The cache driver to use',
|
||||
];
|
||||
|
||||
/**
|
||||
* Clears the cache
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$config = config(Cache::class);
|
||||
$handler = $params[0] ?? $config->handler;
|
||||
|
||||
if (! array_key_exists($handler, $config->validHandlers)) {
|
||||
CLI::error($handler . ' is not a valid cache handler.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$config->handler = $handler;
|
||||
$cache = CacheFactory::getHandler($config);
|
||||
|
||||
if (! $cache->clean()) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Error while clearing the cache.');
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
CLI::write(CLI::color('Cache cleared.', 'green'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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\Cache;
|
||||
|
||||
use CodeIgniter\Cache\CacheFactory;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Cache;
|
||||
|
||||
/**
|
||||
* Shows information on the cache.
|
||||
*/
|
||||
class InfoCache extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Command grouping.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Cache';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cache:info';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Shows file cache information in the current system.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'cache:info';
|
||||
|
||||
/**
|
||||
* Clears the cache
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$config = config(Cache::class);
|
||||
helper('number');
|
||||
|
||||
if ($config->handler !== 'file') {
|
||||
CLI::error('This command only supports the file cache handler.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = CacheFactory::getHandler($config);
|
||||
$caches = $cache->getCacheInfo();
|
||||
$tbody = [];
|
||||
|
||||
foreach ($caches as $key => $field) {
|
||||
$tbody[] = [
|
||||
$key,
|
||||
clean_path($field['server_path']),
|
||||
number_to_size($field['size']),
|
||||
Time::createFromTimestamp($field['date']),
|
||||
];
|
||||
}
|
||||
|
||||
$thead = [
|
||||
CLI::color('Name', 'green'),
|
||||
CLI::color('Server Path', 'green'),
|
||||
CLI::color('Size', 'green'),
|
||||
CLI::color('Date', 'green'),
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Config\Factories;
|
||||
use CodeIgniter\Database\SQLite3\Connection;
|
||||
use Config\Database;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Creates a new database.
|
||||
*/
|
||||
class CreateDatabase extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:create';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new database schema.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'db:create <db_name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'db_name' => 'The database name to use',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--ext' => 'File extension of the database file for SQLite3. Can be `db` or `sqlite`. Defaults to `db`.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new database.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$name = array_shift($params);
|
||||
|
||||
if (empty($name)) {
|
||||
$name = CLI::prompt('Database name', null, 'required'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
try {
|
||||
$config = config(Database::class);
|
||||
|
||||
// Set to an empty database to prevent connection errors.
|
||||
$group = ENVIRONMENT === 'testing' ? 'tests' : $config->defaultGroup;
|
||||
|
||||
$config->{$group}['database'] = '';
|
||||
|
||||
$db = Database::connect();
|
||||
|
||||
// Special SQLite3 handling
|
||||
if ($db instanceof Connection) {
|
||||
$ext = $params['ext'] ?? CLI::getOption('ext') ?? 'db';
|
||||
|
||||
if (! in_array($ext, ['db', 'sqlite'], true)) {
|
||||
$ext = CLI::prompt('Please choose a valid file extension', ['db', 'sqlite']); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if ($name !== ':memory:') {
|
||||
$name = str_replace(['.db', '.sqlite'], '', $name) . ".{$ext}";
|
||||
}
|
||||
|
||||
$config->{$group}['DBDriver'] = 'SQLite3';
|
||||
$config->{$group}['database'] = $name;
|
||||
|
||||
if ($name !== ':memory:') {
|
||||
$dbName = ! str_contains($name, DIRECTORY_SEPARATOR) ? WRITEPATH . $name : $name;
|
||||
|
||||
if (is_file($dbName)) {
|
||||
CLI::error("Database \"{$dbName}\" already exists.", 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
unset($dbName);
|
||||
}
|
||||
|
||||
// Connect to new SQLite3 to create new database
|
||||
$db = Database::connect(null, false);
|
||||
$db->connect();
|
||||
|
||||
if (! is_file($db->getDatabase()) && $name !== ':memory:') {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Database creation failed.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
} elseif (! Database::forge()->createDatabase($name)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Database creation failed.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
CLI::write("Database \"{$name}\" successfully created.", 'green');
|
||||
CLI::newLine();
|
||||
} catch (Throwable $e) {
|
||||
$this->showError($e);
|
||||
} finally {
|
||||
Factories::reset('config');
|
||||
Database::connect(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Runs all new migrations.
|
||||
*/
|
||||
class Migrate extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Locates and runs all new migrations against the database.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'migrate [options]';
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-n' => 'Set migration namespace',
|
||||
'-g' => 'Set database group',
|
||||
'--all' => 'Set for all namespaces, will ignore (-n) option',
|
||||
];
|
||||
|
||||
/**
|
||||
* Ensures that all migrations have been run.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$runner = service('migrations');
|
||||
$runner->clearCliMessages();
|
||||
|
||||
CLI::write(lang('Migrations.latest'), 'yellow');
|
||||
|
||||
$namespace = $params['n'] ?? CLI::getOption('n');
|
||||
$group = $params['g'] ?? CLI::getOption('g');
|
||||
|
||||
try {
|
||||
if (array_key_exists('all', $params) || CLI::getOption('all')) {
|
||||
$runner->setNamespace(null);
|
||||
} elseif ($namespace) {
|
||||
$runner->setNamespace($namespace);
|
||||
}
|
||||
|
||||
if (! $runner->latest($group)) {
|
||||
CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$messages = $runner->getCliMessages();
|
||||
|
||||
foreach ($messages as $message) {
|
||||
CLI::write($message);
|
||||
}
|
||||
|
||||
CLI::write(lang('Migrations.migrated'), 'green');
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Throwable $e) {
|
||||
$this->showError($e);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Does a rollback followed by a latest to refresh the current state
|
||||
* of the database.
|
||||
*/
|
||||
class MigrateRefresh extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:refresh';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Does a rollback followed by a latest to refresh the current state of the database.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'migrate:refresh [options]';
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-n' => 'Set migration namespace',
|
||||
'-g' => 'Set database group',
|
||||
'--all' => 'Set latest for all namespace, will ignore (-n) option',
|
||||
'-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment',
|
||||
];
|
||||
|
||||
/**
|
||||
* Does a rollback followed by a latest to refresh the current state
|
||||
* of the database.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$params['b'] = 0;
|
||||
|
||||
if (ENVIRONMENT === 'production') {
|
||||
// @codeCoverageIgnoreStart
|
||||
$force = array_key_exists('f', $params) || CLI::getOption('f');
|
||||
|
||||
if (! $force && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') {
|
||||
return;
|
||||
}
|
||||
|
||||
$params['f'] = null;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$this->call('migrate:rollback', $params);
|
||||
$this->call('migrate', $params);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Database\MigrationRunner;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Runs all of the migrations in reverse order, until they have
|
||||
* all been unapplied.
|
||||
*/
|
||||
class MigrateRollback extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:rollback';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Runs the "down" method for all migrations in the last batch.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'migrate:rollback [options]';
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3',
|
||||
'-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment',
|
||||
];
|
||||
|
||||
/**
|
||||
* Runs all of the migrations in reverse order, until they have
|
||||
* all been unapplied.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
if (ENVIRONMENT === 'production') {
|
||||
// @codeCoverageIgnoreStart
|
||||
$force = array_key_exists('f', $params) || CLI::getOption('f');
|
||||
|
||||
if (! $force && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') {
|
||||
return null;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/** @var MigrationRunner $runner */
|
||||
$runner = service('migrations');
|
||||
|
||||
try {
|
||||
$batch = $params['b'] ?? CLI::getOption('b') ?? $runner->getLastBatch() - 1;
|
||||
|
||||
if (is_string($batch)) {
|
||||
if (! ctype_digit($batch)) {
|
||||
CLI::error('Invalid batch number: ' . $batch, 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$batch = (int) $batch;
|
||||
}
|
||||
|
||||
CLI::write(lang('Migrations.rollingBack') . ' ' . $batch, 'yellow');
|
||||
|
||||
if (! $runner->regress($batch)) {
|
||||
CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$messages = $runner->getCliMessages();
|
||||
|
||||
foreach ($messages as $message) {
|
||||
CLI::write($message);
|
||||
}
|
||||
|
||||
CLI::write('Done rolling back migrations.', 'green');
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Throwable $e) {
|
||||
$this->showError($e);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Displays a list of all migrations and whether they've been run or not.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Database\MigrateStatusTest
|
||||
*/
|
||||
class MigrateStatus extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:status';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Displays a list of all migrations and whether they\'ve been run or not.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'migrate:status [options]';
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'-g' => 'Set database group',
|
||||
];
|
||||
|
||||
/**
|
||||
* Namespaces to ignore when looking for migrations.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $ignoredNamespaces = [
|
||||
'CodeIgniter',
|
||||
'Config',
|
||||
'Kint',
|
||||
'Laminas\ZendFrameworkBridge',
|
||||
'Laminas\Escaper',
|
||||
'Psr\Log',
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays a list of all migrations and whether they've been run or not.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$runner = service('migrations');
|
||||
$paramGroup = $params['g'] ?? CLI::getOption('g');
|
||||
|
||||
// Get all namespaces
|
||||
$namespaces = service('autoloader')->getNamespace();
|
||||
|
||||
// Collection of migration status
|
||||
$status = [];
|
||||
|
||||
foreach (array_keys($namespaces) as $namespace) {
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
// Make Tests\\Support discoverable for testing
|
||||
$this->ignoredNamespaces[] = 'Tests\Support'; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (in_array($namespace, $this->ignoredNamespaces, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (APP_NAMESPACE !== 'App' && $namespace === 'App') {
|
||||
continue; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$migrations = $runner->findNamespaceMigrations($namespace);
|
||||
|
||||
if (empty($migrations)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$runner->setNamespace($namespace);
|
||||
$history = $runner->getHistory((string) $paramGroup);
|
||||
ksort($migrations);
|
||||
|
||||
foreach ($migrations as $uid => $migration) {
|
||||
$migrations[$uid]->name = mb_substr($migration->name, (int) mb_strpos($migration->name, $uid . '_'));
|
||||
|
||||
$date = '---';
|
||||
$group = '---';
|
||||
$batch = '---';
|
||||
|
||||
foreach ($history as $row) {
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($runner->getObjectUid($row) !== $migration->uid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$date = date('Y-m-d H:i:s', (int) $row->time);
|
||||
$group = $row->group;
|
||||
$batch = $row->batch;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$status[] = [
|
||||
$namespace,
|
||||
$migration->version,
|
||||
$migration->name,
|
||||
$group,
|
||||
$date,
|
||||
$batch,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($status === []) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error(lang('Migrations.noneFound'), 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$headers = [
|
||||
CLI::color(lang('Migrations.namespace'), 'yellow'),
|
||||
CLI::color(lang('Migrations.version'), 'yellow'),
|
||||
CLI::color(lang('Migrations.filename'), 'yellow'),
|
||||
CLI::color(lang('Migrations.group'), 'yellow'),
|
||||
CLI::color(str_replace(': ', '', lang('Migrations.on')), 'yellow'),
|
||||
CLI::color(lang('Migrations.batch'), 'yellow'),
|
||||
];
|
||||
|
||||
CLI::table($status, $headers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Config\Database;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Runs the specified Seeder file to populate the database
|
||||
* with some data.
|
||||
*/
|
||||
class Seed extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:seed';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Runs the specified seeder to populate known data into the database.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'db:seed <seeder_name>';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'seeder_name' => 'The seeder name to run',
|
||||
];
|
||||
|
||||
/**
|
||||
* Passes to Seeder to populate the database.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$seeder = new Seeder(new Database());
|
||||
$seedName = array_shift($params);
|
||||
|
||||
if (empty($seedName)) {
|
||||
$seedName = CLI::prompt(lang('Migrations.migSeeder'), null, 'required'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
try {
|
||||
$seeder->call($seedName);
|
||||
} catch (Throwable $e) {
|
||||
$this->showError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
<?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\Database;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\TableName;
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use Config\Database;
|
||||
|
||||
/**
|
||||
* Get table data if it exists in the database.
|
||||
*
|
||||
* @see \CodeIgniter\Commands\Database\ShowTableInfoTest
|
||||
*/
|
||||
class ShowTableInfo extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Database';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:table';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Retrieves information on the selected table.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = <<<'EOL'
|
||||
db:table [<table_name>] [options]
|
||||
|
||||
Examples:
|
||||
db:table --show
|
||||
db:table --metadata
|
||||
db:table my_table --metadata
|
||||
db:table my_table
|
||||
db:table my_table --limit-rows 5 --limit-field-value 10 --desc
|
||||
EOL;
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'table_name' => 'The table name to show info',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--show' => 'Lists the names of all database tables.',
|
||||
'--metadata' => 'Retrieves list containing field information.',
|
||||
'--desc' => 'Sorts the table rows in DESC order.',
|
||||
'--limit-rows' => 'Limits the number of rows. Default: 10.',
|
||||
'--limit-field-value' => 'Limits the length of field values. Default: 15.',
|
||||
'--dbgroup' => 'Database group to show.',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<list<int|string>> Table Data.
|
||||
*/
|
||||
private array $tbody;
|
||||
|
||||
private ?BaseConnection $db = null;
|
||||
|
||||
/**
|
||||
* @var bool Sort the table rows in DESC order or not.
|
||||
*/
|
||||
private bool $sortDesc = false;
|
||||
|
||||
private string $DBPrefix;
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$dbGroup = $params['dbgroup'] ?? CLI::getOption('dbgroup');
|
||||
|
||||
try {
|
||||
$this->db = Database::connect($dbGroup);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
CLI::error($e->getMessage());
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$this->DBPrefix = $this->db->getPrefix();
|
||||
|
||||
$this->showDBConfig();
|
||||
|
||||
$tables = $this->db->listTables();
|
||||
|
||||
if (array_key_exists('desc', $params)) {
|
||||
$this->sortDesc = true;
|
||||
}
|
||||
|
||||
if ($tables === []) {
|
||||
CLI::error('Database has no tables!', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
if (array_key_exists('show', $params)) {
|
||||
$this->showAllTables($tables);
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$tableName = $params[0] ?? null;
|
||||
$limitRows = (int) ($params['limit-rows'] ?? 10);
|
||||
$limitFieldValue = (int) ($params['limit-field-value'] ?? 15);
|
||||
|
||||
while (! in_array($tableName, $tables, true)) {
|
||||
$tableNameNo = CLI::promptByKey(
|
||||
['Here is the list of your database tables:', 'Which table do you want to see?'],
|
||||
$tables,
|
||||
'required',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
$tableName = $tables[$tableNameNo] ?? null;
|
||||
}
|
||||
|
||||
if (array_key_exists('metadata', $params)) {
|
||||
$this->showFieldMetaData($tableName);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
$this->showDataOfTable($tableName, $limitRows, $limitFieldValue);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
private function showDBConfig(): void
|
||||
{
|
||||
$data = [[
|
||||
'hostname' => $this->db->hostname,
|
||||
'database' => $this->db->getDatabase(),
|
||||
'username' => $this->db->username,
|
||||
'DBDriver' => $this->db->getPlatform(),
|
||||
'DBPrefix' => $this->DBPrefix,
|
||||
'port' => $this->db->port,
|
||||
]];
|
||||
CLI::table(
|
||||
$data,
|
||||
['hostname', 'database', 'username', 'DBDriver', 'DBPrefix', 'port'],
|
||||
);
|
||||
}
|
||||
|
||||
private function removeDBPrefix(): void
|
||||
{
|
||||
$this->db->setPrefix('');
|
||||
}
|
||||
|
||||
private function restoreDBPrefix(): void
|
||||
{
|
||||
$this->db->setPrefix($this->DBPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Data of Table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function showDataOfTable(string $tableName, int $limitRows, int $limitFieldValue)
|
||||
{
|
||||
CLI::write("Data of Table \"{$tableName}\":", 'black', 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
$this->removeDBPrefix();
|
||||
$thead = $this->db->getFieldNames(TableName::fromActualName($this->db->DBPrefix, $tableName));
|
||||
$this->restoreDBPrefix();
|
||||
|
||||
// If there is a field named `id`, sort by it.
|
||||
$sortField = null;
|
||||
if (in_array('id', $thead, true)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$this->tbody = $this->makeTableRows($tableName, $limitRows, $limitFieldValue, $sortField);
|
||||
CLI::table($this->tbody, $thead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show All Tables
|
||||
*
|
||||
* @param list<string> $tables
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function showAllTables(array $tables)
|
||||
{
|
||||
CLI::write('The following is a list of the names of all database tables:', 'black', 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
$thead = ['ID', 'Table Name', 'Num of Rows', 'Num of Fields'];
|
||||
$this->tbody = $this->makeTbodyForShowAllTables($tables);
|
||||
|
||||
CLI::table($this->tbody, $thead);
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make body for table
|
||||
*
|
||||
* @param list<string> $tables
|
||||
*
|
||||
* @return list<list<int|string>>
|
||||
*/
|
||||
private function makeTbodyForShowAllTables(array $tables): array
|
||||
{
|
||||
$this->removeDBPrefix();
|
||||
|
||||
foreach ($tables as $id => $tableName) {
|
||||
$table = $this->db->protectIdentifiers($tableName);
|
||||
$db = $this->db->query("SELECT * FROM {$table}");
|
||||
|
||||
$this->tbody[] = [
|
||||
$id + 1,
|
||||
$tableName,
|
||||
$db->getNumRows(),
|
||||
$db->getFieldCount(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->restoreDBPrefix();
|
||||
|
||||
if ($this->sortDesc) {
|
||||
krsort($this->tbody);
|
||||
}
|
||||
|
||||
return $this->tbody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make table rows
|
||||
*
|
||||
* @return list<list<int|string>>
|
||||
*/
|
||||
private function makeTableRows(
|
||||
string $tableName,
|
||||
int $limitRows,
|
||||
int $limitFieldValue,
|
||||
?string $sortField = null,
|
||||
): array {
|
||||
$this->tbody = [];
|
||||
|
||||
$this->removeDBPrefix();
|
||||
$builder = $this->db->table(TableName::fromActualName($this->db->DBPrefix, $tableName));
|
||||
$builder->limit($limitRows);
|
||||
if ($sortField !== null) {
|
||||
$builder->orderBy($sortField, $this->sortDesc ? 'DESC' : 'ASC');
|
||||
}
|
||||
$rows = $builder->get()->getResultArray();
|
||||
$this->restoreDBPrefix();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$row = array_map(
|
||||
static fn ($item): string => mb_strlen((string) $item) > $limitFieldValue
|
||||
? mb_substr((string) $item, 0, $limitFieldValue) . '...'
|
||||
: (string) $item,
|
||||
$row,
|
||||
);
|
||||
$this->tbody[] = $row;
|
||||
}
|
||||
|
||||
if ($sortField === null && $this->sortDesc) {
|
||||
krsort($this->tbody);
|
||||
}
|
||||
|
||||
return $this->tbody;
|
||||
}
|
||||
|
||||
private function showFieldMetaData(string $tableName): void
|
||||
{
|
||||
CLI::write("List of Metadata Information in Table \"{$tableName}\":", 'black', 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
$thead = ['Field Name', 'Type', 'Max Length', 'Nullable', 'Default', 'Primary Key'];
|
||||
|
||||
$this->removeDBPrefix();
|
||||
$fields = $this->db->getFieldData($tableName);
|
||||
$this->restoreDBPrefix();
|
||||
|
||||
foreach ($fields as $row) {
|
||||
$this->tbody[] = [
|
||||
$row->name,
|
||||
$row->type,
|
||||
$row->max_length,
|
||||
isset($row->nullable) ? $this->setYesOrNo($row->nullable) : 'n/a',
|
||||
$row->default,
|
||||
isset($row->primary_key) ? $this->setYesOrNo($row->primary_key) : 'n/a',
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->sortDesc) {
|
||||
krsort($this->tbody);
|
||||
}
|
||||
|
||||
CLI::table($this->tbody, $thead);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|int|string|null $fieldValue
|
||||
*/
|
||||
private function setYesOrNo($fieldValue): string
|
||||
{
|
||||
if ((bool) $fieldValue) {
|
||||
return CLI::color('Yes', 'green');
|
||||
}
|
||||
|
||||
return CLI::color('No', 'red');
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
<?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\Encryption;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Config\DotEnv;
|
||||
use CodeIgniter\Encryption\Encryption;
|
||||
|
||||
/**
|
||||
* Generates a new encryption key.
|
||||
*/
|
||||
class GenerateKey extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The Command's group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Encryption';
|
||||
|
||||
/**
|
||||
* The Command's name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'key:generate';
|
||||
|
||||
/**
|
||||
* The Command's usage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'key:generate [options]';
|
||||
|
||||
/**
|
||||
* The Command's short description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new encryption key and writes it in an `.env` file.';
|
||||
|
||||
/**
|
||||
* The command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--force' => 'Force overwrite existing key in `.env` file.',
|
||||
'--length' => 'The length of the random string that should be returned in bytes. Defaults to 32.',
|
||||
'--prefix' => 'Prefix to prepend to encoded key (either hex2bin or base64). Defaults to hex2bin.',
|
||||
'--show' => 'Shows the generated key in the terminal instead of storing in the `.env` file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute the command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$prefix = $params['prefix'] ?? CLI::getOption('prefix');
|
||||
|
||||
if (in_array($prefix, [null, true], true)) {
|
||||
$prefix = 'hex2bin';
|
||||
} elseif (! in_array($prefix, ['hex2bin', 'base64'], true)) {
|
||||
$prefix = CLI::prompt('Please provide a valid prefix to use.', ['hex2bin', 'base64'], 'required'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$length = $params['length'] ?? CLI::getOption('length');
|
||||
|
||||
if (in_array($length, [null, true], true)) {
|
||||
$length = 32;
|
||||
}
|
||||
|
||||
$encodedKey = $this->generateRandomKey($prefix, $length);
|
||||
|
||||
if (array_key_exists('show', $params) || (bool) CLI::getOption('show')) {
|
||||
CLI::write($encodedKey, 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->setNewEncryptionKey($encodedKey, $params)) {
|
||||
CLI::write('Error in setting new encryption key to .env file.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// force DotEnv to reload the new env vars
|
||||
putenv('encryption.key');
|
||||
unset($_ENV['encryption.key'], $_SERVER['encryption.key']);
|
||||
$dotenv = new DotEnv(ROOTPATH);
|
||||
$dotenv->load();
|
||||
|
||||
CLI::write('Application\'s new encryption key was successfully set.', 'green');
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key and encodes it.
|
||||
*/
|
||||
protected function generateRandomKey(string $prefix, int $length): string
|
||||
{
|
||||
$key = Encryption::createKey($length);
|
||||
|
||||
if ($prefix === 'hex2bin') {
|
||||
return 'hex2bin:' . bin2hex($key);
|
||||
}
|
||||
|
||||
return 'base64:' . base64_encode($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new encryption key in your .env file.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function setNewEncryptionKey(string $key, array $params): bool
|
||||
{
|
||||
$currentKey = env('encryption.key', '');
|
||||
|
||||
if ($currentKey !== '' && ! $this->confirmOverwrite($params)) {
|
||||
// Not yet testable since it requires keyboard input
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $this->writeNewEncryptionKeyToFile($currentKey, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether to overwrite existing encryption key.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function confirmOverwrite(array $params): bool
|
||||
{
|
||||
return (array_key_exists('force', $params) || CLI::getOption('force')) || CLI::prompt('Overwrite existing key?', ['n', 'y']) === 'y';
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the new encryption key to .env file.
|
||||
*/
|
||||
protected function writeNewEncryptionKeyToFile(string $oldKey, string $newKey): 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('Here\'s your new key instead: ' . CLI::color($newKey, 'yellow'));
|
||||
CLI::newLine();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
copy($baseEnv, $envFile);
|
||||
}
|
||||
|
||||
$oldFileContents = (string) file_get_contents($envFile);
|
||||
$replacementKey = "\nencryption.key = {$newKey}";
|
||||
|
||||
if (! str_contains($oldFileContents, 'encryption.key')) {
|
||||
return file_put_contents($envFile, $replacementKey, FILE_APPEND) !== false;
|
||||
}
|
||||
|
||||
$newFileContents = preg_replace($this->keyPattern($oldKey), $replacementKey, $oldFileContents);
|
||||
|
||||
if ($newFileContents === $oldFileContents) {
|
||||
$newFileContents = preg_replace(
|
||||
'/^[#\s]*encryption.key[=\s]*(?:hex2bin\:[a-f0-9]{64}|base64\:(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?)$/m',
|
||||
$replacementKey,
|
||||
$oldFileContents,
|
||||
);
|
||||
}
|
||||
|
||||
return file_put_contents($envFile, $newFileContents) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regex of the current encryption key.
|
||||
*/
|
||||
protected function keyPattern(string $oldKey): string
|
||||
{
|
||||
$escaped = preg_quote($oldKey, '/');
|
||||
|
||||
if ($escaped !== '') {
|
||||
$escaped = "[{$escaped}]*";
|
||||
}
|
||||
|
||||
return "/^[#\\s]*encryption.key[=\\s]*{$escaped}$/m";
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
use Config\Generators;
|
||||
|
||||
/**
|
||||
* Generates a skeleton Cell and its view.
|
||||
*/
|
||||
class CellGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:cell';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new Controlled Cell file and its view.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:cell <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The Controlled Cell class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Cell';
|
||||
$this->directory = 'Cells';
|
||||
|
||||
$params = array_merge($params, ['suffix' => null]);
|
||||
|
||||
$this->templatePath = config(Generators::class)->views[$this->name]['class'];
|
||||
$this->template = 'cell.tpl.php';
|
||||
$this->classNameLang = 'CLI.generator.className.cell';
|
||||
|
||||
$this->generateClass($params);
|
||||
|
||||
$this->templatePath = config(Generators::class)->views[$this->name]['view'];
|
||||
$this->template = 'cell_view.tpl.php';
|
||||
$this->classNameLang = 'CLI.generator.viewName.cell';
|
||||
|
||||
$className = $this->qualifyClassName();
|
||||
$viewName = decamelize(class_basename($className));
|
||||
$viewName = preg_replace(
|
||||
'/([a-z][a-z0-9_\/\\\\]+)(_cell)$/i',
|
||||
'$1',
|
||||
$viewName,
|
||||
) ?? $viewName;
|
||||
$namespace = substr($className, 0, strrpos($className, '\\') + 1);
|
||||
|
||||
$this->generateView($namespace . $viewName, $params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton command file.
|
||||
*/
|
||||
class CommandGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:command';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new spark command.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:command <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The command class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--command' => 'The command name. Default: "command:name"',
|
||||
'--type' => 'The command type. Options [basic, generator]. Default: "basic".',
|
||||
'--group' => 'The command group. Default: [basic -> "App", generator -> "Generators"].',
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserCommand).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Command';
|
||||
$this->directory = 'Commands';
|
||||
$this->template = 'command.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.command';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
$command = $this->getOption('command');
|
||||
$group = $this->getOption('group');
|
||||
$type = $this->getOption('type');
|
||||
|
||||
$command = is_string($command) ? $command : 'command:name';
|
||||
$type = is_string($type) ? $type : 'basic';
|
||||
|
||||
if (! in_array($type, ['basic', 'generator'], true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$type = CLI::prompt(lang('CLI.generator.commandType'), ['basic', 'generator'], 'required');
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if (! is_string($group)) {
|
||||
$group = $type === 'generator' ? 'Generators' : 'App';
|
||||
}
|
||||
|
||||
return $this->parseTemplate(
|
||||
$class,
|
||||
['{group}', '{command}'],
|
||||
[$group, $command],
|
||||
['type' => $type],
|
||||
);
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton config file.
|
||||
*/
|
||||
class ConfigGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:config';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new config file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:config <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The config class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserConfig).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Config';
|
||||
$this->directory = 'Config';
|
||||
$this->template = 'config.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.config';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
$namespace = $this->getOption('namespace') ?? APP_NAMESPACE;
|
||||
|
||||
if ($namespace === APP_NAMESPACE) {
|
||||
$class = substr($class, strlen($namespace . '\\'));
|
||||
}
|
||||
|
||||
return $this->parseTemplate($class);
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\RESTful\ResourceController;
|
||||
use CodeIgniter\RESTful\ResourcePresenter;
|
||||
|
||||
/**
|
||||
* Generates a skeleton controller file.
|
||||
*/
|
||||
class ControllerGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:controller';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new controller file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:controller <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The controller class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--bare' => 'Extends from CodeIgniter\Controller instead of BaseController.',
|
||||
'--restful' => 'Extends from a RESTful resource, Options: [controller, presenter]. Default: "controller".',
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserController).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Controller';
|
||||
$this->directory = 'Controllers';
|
||||
$this->template = 'controller.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.controller';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
$bare = $this->getOption('bare');
|
||||
$rest = $this->getOption('restful');
|
||||
|
||||
$useStatement = trim(APP_NAMESPACE, '\\') . '\Controllers\BaseController';
|
||||
$extends = 'BaseController';
|
||||
|
||||
// Gets the appropriate parent class to extend.
|
||||
if ($bare || $rest) {
|
||||
if ($bare) {
|
||||
$useStatement = Controller::class;
|
||||
$extends = 'Controller';
|
||||
} elseif ($rest) {
|
||||
$rest = is_string($rest) ? $rest : 'controller';
|
||||
|
||||
if (! in_array($rest, ['controller', 'presenter'], true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$rest = CLI::prompt(lang('CLI.generator.parentClass'), ['controller', 'presenter'], 'required');
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($rest === 'controller') {
|
||||
$useStatement = ResourceController::class;
|
||||
$extends = 'ResourceController';
|
||||
} elseif ($rest === 'presenter') {
|
||||
$useStatement = ResourcePresenter::class;
|
||||
$extends = 'ResourcePresenter';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->parseTemplate(
|
||||
$class,
|
||||
['{useStatement}', '{extends}'],
|
||||
[$useStatement, $extends],
|
||||
['type' => $rest],
|
||||
);
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton Entity file.
|
||||
*/
|
||||
class EntityGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:entity';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new entity file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:entity <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The entity class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserEntity).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Entity';
|
||||
$this->directory = 'Entities';
|
||||
$this->template = 'entity.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.entity';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton Filter file.
|
||||
*/
|
||||
class FilterGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:filter';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new filter file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:filter <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The filter class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserFilter).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Filter';
|
||||
$this->directory = 'Filters';
|
||||
$this->template = 'filter.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.filter';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
use Config\Database;
|
||||
use Config\Migrations;
|
||||
use Config\Session as SessionConfig;
|
||||
|
||||
/**
|
||||
* Generates a skeleton migration file.
|
||||
*/
|
||||
class MigrationGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:migration';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new migration file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:migration <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The migration class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--session' => 'Generates the migration file for database sessions.',
|
||||
'--table' => 'Table name to use for database sessions. Default: "ci_sessions".',
|
||||
'--dbgroup' => 'Database group to use for database sessions. Default: "default".',
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserMigration).',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Migration';
|
||||
$this->directory = 'Database\Migrations';
|
||||
$this->template = 'migration.tpl.php';
|
||||
|
||||
if (array_key_exists('session', $params) || CLI::getOption('session')) {
|
||||
$table = $params['table'] ?? CLI::getOption('table') ?? 'ci_sessions';
|
||||
$params[0] = "_create_{$table}_table";
|
||||
}
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.migration';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
$data = [];
|
||||
$data['session'] = false;
|
||||
|
||||
if ($this->getOption('session')) {
|
||||
$table = $this->getOption('table');
|
||||
$DBGroup = $this->getOption('dbgroup');
|
||||
|
||||
$data['session'] = true;
|
||||
$data['table'] = is_string($table) ? $table : 'ci_sessions';
|
||||
$data['DBGroup'] = is_string($DBGroup) ? $DBGroup : 'default';
|
||||
$data['DBDriver'] = config(Database::class)->{$data['DBGroup']}['DBDriver'];
|
||||
|
||||
$data['matchIP'] = config(SessionConfig::class)->matchIP;
|
||||
}
|
||||
|
||||
return $this->parseTemplate($class, [], [], $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change file basename before saving.
|
||||
*/
|
||||
protected function basename(string $filename): string
|
||||
{
|
||||
return gmdate(config(Migrations::class)->timestampFormat) . basename($filename);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton Model file.
|
||||
*/
|
||||
class ModelGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:model';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new model file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:model <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The model class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--table' => 'Supply a table name. Default: "the lowercased plural of the class name".',
|
||||
'--dbgroup' => 'Database group to use. Default: "default".',
|
||||
'--return' => 'Return type, Options: [array, object, entity]. Default: "array".',
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserModel).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Model';
|
||||
$this->directory = 'Models';
|
||||
$this->template = 'model.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.model';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
$table = $this->getOption('table');
|
||||
$dbGroup = $this->getOption('dbgroup');
|
||||
$return = $this->getOption('return');
|
||||
|
||||
$baseClass = class_basename($class);
|
||||
|
||||
if (preg_match('/^(\S+)Model$/i', $baseClass, $match) === 1) {
|
||||
$baseClass = $match[1];
|
||||
}
|
||||
|
||||
$table = is_string($table) ? $table : plural(strtolower($baseClass));
|
||||
$return = is_string($return) ? $return : 'array';
|
||||
|
||||
if (! in_array($return, ['array', 'object', 'entity'], true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$return = CLI::prompt(lang('CLI.generator.returnType'), ['array', 'object', 'entity'], 'required');
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($return === 'entity') {
|
||||
$return = str_replace('Models', 'Entities', $class);
|
||||
|
||||
if (preg_match('/^(\S+)Model$/i', $return, $match) === 1) {
|
||||
$return = $match[1];
|
||||
|
||||
if ($this->getOption('suffix')) {
|
||||
$return .= 'Entity';
|
||||
}
|
||||
}
|
||||
|
||||
$return = '\\' . trim($return, '\\') . '::class';
|
||||
$this->call('make:entity', array_merge([$baseClass], $this->params));
|
||||
} else {
|
||||
$return = "'{$return}'";
|
||||
}
|
||||
|
||||
return $this->parseTemplate($class, ['{dbGroup}', '{table}', '{return}'], [$dbGroup, $table, $return], compact('dbGroup'));
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a complete set of scaffold files.
|
||||
*/
|
||||
class ScaffoldGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:scaffold';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a complete set of scaffold files.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:scaffold <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The class name',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--bare' => 'Add the "--bare" option to controller component.',
|
||||
'--restful' => 'Add the "--restful" option to controller component.',
|
||||
'--table' => 'Add the "--table" option to the model component.',
|
||||
'--dbgroup' => 'Add the "--dbgroup" option to model component.',
|
||||
'--return' => 'Add the "--return" option to the model component.',
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name.',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
$options = [];
|
||||
|
||||
if ($this->getOption('namespace')) {
|
||||
$options['namespace'] = $this->getOption('namespace');
|
||||
}
|
||||
|
||||
if ($this->getOption('suffix')) {
|
||||
$options['suffix'] = null;
|
||||
}
|
||||
|
||||
if ($this->getOption('force')) {
|
||||
$options['force'] = null;
|
||||
}
|
||||
|
||||
$controllerOpts = [];
|
||||
|
||||
if ($this->getOption('bare')) {
|
||||
$controllerOpts['bare'] = null;
|
||||
} elseif ($this->getOption('restful')) {
|
||||
$controllerOpts['restful'] = $this->getOption('restful');
|
||||
}
|
||||
|
||||
$modelOpts = [
|
||||
'table' => $this->getOption('table'),
|
||||
'dbgroup' => $this->getOption('dbgroup'),
|
||||
'return' => $this->getOption('return'),
|
||||
];
|
||||
|
||||
$class = $params[0] ?? CLI::getSegment(2);
|
||||
|
||||
// Call those commands!
|
||||
$this->call('make:controller', array_merge([$class], $controllerOpts, $options));
|
||||
$this->call('make:model', array_merge([$class], $modelOpts, $options));
|
||||
$this->call('make:migration', array_merge([$class], $options));
|
||||
$this->call('make:seeder', array_merge([$class], $options));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton seeder file.
|
||||
*/
|
||||
class SeederGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new seeder file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:seeder <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The seeder class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserSeeder).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Seeder';
|
||||
$this->directory = 'Database\Seeds';
|
||||
$this->template = 'seeder.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.seeder';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton command file.
|
||||
*/
|
||||
class TestGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:test';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new test file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:test <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The test class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "Tests".',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Test';
|
||||
$this->template = 'test.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.test';
|
||||
|
||||
$autoload = service('autoloader');
|
||||
$autoload->addNamespace('CodeIgniter', TESTPATH . 'system');
|
||||
$autoload->addNamespace('Tests', ROOTPATH . 'tests');
|
||||
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace from input or the default namespace.
|
||||
*/
|
||||
protected function getNamespace(): string
|
||||
{
|
||||
if ($this->namespace !== null) {
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
if ($this->getOption('namespace') !== null) {
|
||||
return trim(
|
||||
str_replace(
|
||||
'/',
|
||||
'\\',
|
||||
$this->getOption('namespace'),
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
}
|
||||
|
||||
$class = $this->normalizeInputClassName();
|
||||
$classPaths = explode('\\', $class);
|
||||
|
||||
$namespaces = service('autoloader')->getNamespace();
|
||||
|
||||
while ($classPaths !== []) {
|
||||
array_pop($classPaths);
|
||||
$namespace = implode('\\', $classPaths);
|
||||
|
||||
foreach (array_keys($namespaces) as $prefix) {
|
||||
if ($prefix === $namespace) {
|
||||
// The input classname is FQCN, and use the namespace.
|
||||
return $namespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'Tests';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the test file path from the class name.
|
||||
*
|
||||
* @param string $class namespaced classname.
|
||||
*/
|
||||
protected function buildPath(string $class): string
|
||||
{
|
||||
$namespace = $this->getNamespace();
|
||||
|
||||
$base = $this->searchTestFilePath($namespace);
|
||||
|
||||
if ($base === null) {
|
||||
CLI::error(
|
||||
lang('CLI.namespaceNotDefined', [$namespace]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$realpath = realpath($base);
|
||||
$base = ($realpath !== false) ? $realpath : $base;
|
||||
|
||||
$file = $base . DIRECTORY_SEPARATOR
|
||||
. str_replace(
|
||||
'\\',
|
||||
DIRECTORY_SEPARATOR,
|
||||
trim(str_replace($namespace . '\\', '', $class), '\\'),
|
||||
) . '.php';
|
||||
|
||||
return implode(
|
||||
DIRECTORY_SEPARATOR,
|
||||
array_slice(
|
||||
explode(DIRECTORY_SEPARATOR, $file),
|
||||
0,
|
||||
-1,
|
||||
),
|
||||
) . DIRECTORY_SEPARATOR . $this->basename($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns test file path for the namespace.
|
||||
*/
|
||||
private function searchTestFilePath(string $namespace): ?string
|
||||
{
|
||||
$bases = service('autoloader')->getNamespace($namespace);
|
||||
|
||||
$base = null;
|
||||
|
||||
foreach ($bases as $candidate) {
|
||||
if (str_contains($candidate, '/tests/')) {
|
||||
$base = $candidate;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\Generators;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
|
||||
/**
|
||||
* Generates a skeleton Validation file.
|
||||
*/
|
||||
class ValidationGenerator extends BaseCommand
|
||||
{
|
||||
use GeneratorTrait;
|
||||
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Generators';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:validation';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generates a new validation file.';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'make:validation <name> [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'name' => 'The validation class name.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
|
||||
'--suffix' => 'Append the component title to the class name (e.g. User => UserValidation).',
|
||||
'--force' => 'Force overwrite existing file.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->component = 'Validation';
|
||||
$this->directory = 'Validation';
|
||||
$this->template = 'validation.tpl.php';
|
||||
|
||||
$this->classNameLang = 'CLI.generator.className.validation';
|
||||
$this->generateClass($params);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\View\Cells\Cell;
|
||||
|
||||
class {class} extends Cell
|
||||
{
|
||||
//
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<!-- Your HTML here -->
|
||||
</div>
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
<?php if ($type === 'generator'): ?>
|
||||
use CodeIgniter\CLI\GeneratorTrait;
|
||||
<?php endif ?>
|
||||
|
||||
class {class} extends BaseCommand
|
||||
{
|
||||
<?php if ($type === 'generator'): ?>
|
||||
use GeneratorTrait;
|
||||
|
||||
<?php endif ?>
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = '{group}';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '{command}';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* The Command's Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = '{command} [arguments] [options]';
|
||||
|
||||
/**
|
||||
* The Command's Arguments
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The Command's Options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
<?php if ($type === 'generator'): ?>
|
||||
$this->component = 'Command';
|
||||
$this->directory = 'Commands';
|
||||
$this->template = 'command.tpl.php';
|
||||
|
||||
$this->execute($params);
|
||||
<?php else: ?>
|
||||
//
|
||||
<?php endif ?>
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
class {class} extends BaseConfig
|
||||
{
|
||||
//
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use {useStatement};
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class {class} extends {extends}
|
||||
{
|
||||
<?php if ($type === 'controller'): ?>
|
||||
/**
|
||||
* Return an array of resource objects, themselves in array format.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties of a resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new resource object, with default properties.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function new()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new resource object, from "posted" parameters.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the editable properties of a resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function edit($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update a model resource, from "posted" properties.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the designated resource object from the model.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
<?php elseif ($type === 'presenter'): ?>
|
||||
/**
|
||||
* Present a view of resource objects.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a view to present a specific resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a view to present a new single resource object.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function new()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the creation/insertion of a new resource object.
|
||||
* This should be a POST.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a view to edit the properties of a specific resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function edit($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the updating, full or partial, of a specific resource object.
|
||||
* This should be a POST.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a view to confirm the deletion of a specific resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function remove($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the deletion of a specific resource object.
|
||||
*
|
||||
* @param int|string|null $id
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
<?php else: ?>
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
<?php endif ?>
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Entity\Entity;
|
||||
|
||||
class {class} extends Entity
|
||||
{
|
||||
protected $datamap = [];
|
||||
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
|
||||
protected $casts = [];
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class {class} implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Do whatever processing this filter needs to do.
|
||||
* By default it should not return anything during
|
||||
* normal execution. However, when an abnormal state
|
||||
* is found, it should return an instance of
|
||||
* CodeIgniter\HTTP\Response. If it does, script
|
||||
* execution will end and that Response will be
|
||||
* sent back to the client, allowing for error pages,
|
||||
* redirects, etc.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param array|null $arguments
|
||||
*
|
||||
* @return RequestInterface|ResponseInterface|string|void
|
||||
*/
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows After filters to inspect and modify the response
|
||||
* object as needed. This method does not allow any way
|
||||
* to stop execution of other after filters, short of
|
||||
* throwing an Exception or Error.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param array|null $arguments
|
||||
*
|
||||
* @return ResponseInterface|void
|
||||
*/
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class {class} extends Migration
|
||||
{
|
||||
<?php if ($session): ?>
|
||||
protected $DBGroup = '<?= $DBGroup ?>';
|
||||
|
||||
public function up()
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'VARCHAR', 'constraint' => 128, 'null' => false],
|
||||
<?php if ($DBDriver === 'MySQLi'): ?>
|
||||
'ip_address' => ['type' => 'VARCHAR', 'constraint' => 45, 'null' => false],
|
||||
'timestamp timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL',
|
||||
'data' => ['type' => 'BLOB', 'null' => false],
|
||||
<?php elseif ($DBDriver === 'Postgre'): ?>
|
||||
'ip_address inet NOT NULL',
|
||||
'timestamp timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL',
|
||||
"data bytea DEFAULT '' NOT NULL",
|
||||
<?php endif; ?>
|
||||
]);
|
||||
<?php if ($matchIP) : ?>
|
||||
$this->forge->addKey(['id', 'ip_address'], true);
|
||||
<?php else: ?>
|
||||
$this->forge->addKey('id', true);
|
||||
<?php endif ?>
|
||||
$this->forge->addKey('timestamp');
|
||||
$this->forge->createTable('<?= $table ?>', true);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropTable('<?= $table ?>', true);
|
||||
}
|
||||
<?php else: ?>
|
||||
public function up()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
<?php endif ?>
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class {class} extends Model
|
||||
{
|
||||
<?php if (is_string($dbGroup)): ?>
|
||||
protected $DBGroup = '{dbGroup}';
|
||||
<?php endif; ?>
|
||||
protected $table = '{table}';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = {return};
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
protected bool $updateOnlyChanged = true;
|
||||
|
||||
protected array $casts = [];
|
||||
protected array $castHandlers = [];
|
||||
|
||||
// Dates
|
||||
protected $useTimestamps = false;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
protected $deletedField = 'deleted_at';
|
||||
|
||||
// Validation
|
||||
protected $validationRules = [];
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = [];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = [];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class {class} extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
class {class} extends CIUnitTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testExample(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<@php
|
||||
|
||||
namespace {namespace};
|
||||
|
||||
class {class}
|
||||
{
|
||||
// public function custom_rule(): bool
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
|
||||
/**
|
||||
* CI Help command for the spark script.
|
||||
*
|
||||
* Lists the basic usage information for the spark script,
|
||||
* and provides a way to list help for other commands.
|
||||
*/
|
||||
class Help 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 = 'help';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Displays basic usage information.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'help [<command_name>]';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'command_name' => 'The command name [default: "help"]',
|
||||
];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Displays the help for spark commands.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$command = array_shift($params);
|
||||
$command ??= 'help';
|
||||
$commands = $this->commands->getCommands();
|
||||
|
||||
if (! $this->commands->verifyCommand($command, $commands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = new $commands[$command]['class']($this->logger, $this->commands);
|
||||
$class->showHelp();
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?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\Housekeeping;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* ClearDebugbar Command
|
||||
*/
|
||||
class ClearDebugbar extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Housekeeping';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'debugbar:clear';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'debugbar:clear';
|
||||
|
||||
/**
|
||||
* The Command's short description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clears all debugbar JSON files.';
|
||||
|
||||
/**
|
||||
* Actually runs the command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
helper('filesystem');
|
||||
|
||||
if (! delete_files(WRITEPATH . 'debugbar', false, true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Error deleting the debugbar JSON files.');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
CLI::write('Debugbar cleared.', 'green');
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?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\Housekeeping;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* ClearLogs command.
|
||||
*/
|
||||
class ClearLogs extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Housekeeping';
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'logs:clear';
|
||||
|
||||
/**
|
||||
* The Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clears all log files.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'logs:clear [option';
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--force' => 'Force delete of all logs files without prompting.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$force = array_key_exists('force', $params) || CLI::getOption('force');
|
||||
|
||||
if (! $force && CLI::prompt('Are you sure you want to delete the logs?', ['n', 'y']) === 'n') {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Deleting logs aborted.', 'light_gray', 'red');
|
||||
CLI::error('If you want, use the "-force" option to force delete all log files.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
helper('filesystem');
|
||||
|
||||
if (! delete_files(WRITEPATH . 'logs', false, true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error('Error in deleting the logs files.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
CLI::write('Logs cleared.', 'green');
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* CI Help command for the spark script.
|
||||
*
|
||||
* Lists the basic usage information for the spark script,
|
||||
* and provides a way to list help for other commands.
|
||||
*/
|
||||
class ListCommands 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 = 'list';
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Lists the available commands.';
|
||||
|
||||
/**
|
||||
* the Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'list';
|
||||
|
||||
/**
|
||||
* the Command's Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* the Command's Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--simple' => 'Prints a list of the commands with no other info',
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$commands = $this->commands->getCommands();
|
||||
ksort($commands);
|
||||
|
||||
// Check for 'simple' format
|
||||
return array_key_exists('simple', $params) || CLI::getOption('simple') === true
|
||||
? $this->listSimple($commands)
|
||||
: $this->listFull($commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the commands with accompanying info.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function listFull(array $commands)
|
||||
{
|
||||
// Sort into buckets by group
|
||||
$groups = [];
|
||||
|
||||
foreach ($commands as $title => $command) {
|
||||
if (! isset($groups[$command['group']])) {
|
||||
$groups[$command['group']] = [];
|
||||
}
|
||||
|
||||
$groups[$command['group']][$title] = $command;
|
||||
}
|
||||
|
||||
$length = max(array_map(strlen(...), array_keys($commands)));
|
||||
|
||||
ksort($groups);
|
||||
|
||||
// Display it all...
|
||||
foreach ($groups as $group => $commands) {
|
||||
CLI::write($group, 'yellow');
|
||||
|
||||
foreach ($commands as $name => $command) {
|
||||
$name = $this->setPad($name, $length, 2, 2);
|
||||
$output = CLI::color($name, 'green');
|
||||
|
||||
if (isset($command['description'])) {
|
||||
$output .= CLI::wrap($command['description'], 125, strlen($name));
|
||||
}
|
||||
|
||||
CLI::write($output);
|
||||
}
|
||||
|
||||
if ($group !== array_key_last($groups)) {
|
||||
CLI::newLine();
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the commands only.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function listSimple(array $commands)
|
||||
{
|
||||
foreach (array_keys($commands) as $title) {
|
||||
CLI::write($title);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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\Server;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Launch the PHP development server
|
||||
*
|
||||
* Not testable, as it throws phpunit for a loop :-/
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Serve extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'CodeIgniter';
|
||||
|
||||
/**
|
||||
* Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'serve';
|
||||
|
||||
/**
|
||||
* Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Launches the CodeIgniter PHP-Development Server.';
|
||||
|
||||
/**
|
||||
* Usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'serve';
|
||||
|
||||
/**
|
||||
* Arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The current port offset.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $portOffset = 0;
|
||||
|
||||
/**
|
||||
* The max number of ports to attempt to serve from
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $tries = 10;
|
||||
|
||||
/**
|
||||
* Options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [
|
||||
'--php' => 'The PHP Binary [default: "PHP_BINARY"]',
|
||||
'--host' => 'The HTTP Host [default: "localhost"]',
|
||||
'--port' => 'The HTTP Host Port [default: "8080"]',
|
||||
];
|
||||
|
||||
/**
|
||||
* Run the server
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
// Collect any user-supplied options and apply them.
|
||||
$php = escapeshellarg(CLI::getOption('php') ?? PHP_BINARY);
|
||||
$host = CLI::getOption('host') ?? 'localhost';
|
||||
$port = (int) (CLI::getOption('port') ?? 8080) + $this->portOffset;
|
||||
|
||||
// Get the party started.
|
||||
CLI::write('CodeIgniter development server started on http://' . $host . ':' . $port, 'green');
|
||||
CLI::write('Press Control-C to stop.');
|
||||
|
||||
// Set the Front Controller path as Document Root.
|
||||
$docroot = escapeshellarg(FCPATH);
|
||||
|
||||
// Mimic Apache's mod_rewrite functionality with user settings.
|
||||
$rewrite = escapeshellarg(SYSTEMPATH . 'rewrite.php');
|
||||
|
||||
// Call PHP's built-in webserver, making sure to set our
|
||||
// base path to the public folder, and to use the rewrite file
|
||||
// to ensure our environment is set and it simulates basic mod_rewrite.
|
||||
passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite, $status);
|
||||
|
||||
if ($status !== EXIT_SUCCESS && $this->portOffset < $this->tries) {
|
||||
$this->portOffset++;
|
||||
|
||||
$this->run($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
<?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\Translation;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Helpers\Array\ArrayHelper;
|
||||
use Config\App;
|
||||
use Locale;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* @see \CodeIgniter\Commands\Translation\LocalizationFinderTest
|
||||
*/
|
||||
class LocalizationFinder extends BaseCommand
|
||||
{
|
||||
protected $group = 'Translation';
|
||||
protected $name = 'lang:find';
|
||||
protected $description = 'Find and save available phrases to translate.';
|
||||
protected $usage = 'lang:find [options]';
|
||||
protected $arguments = [];
|
||||
protected $options = [
|
||||
'--locale' => 'Specify locale (en, ru, etc.) to save files.',
|
||||
'--dir' => 'Directory to search for translations relative to APPPATH.',
|
||||
'--show-new' => 'Show only new translations in table. Does not write to files.',
|
||||
'--verbose' => 'Output detailed information.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Flag for output detailed information
|
||||
*/
|
||||
private bool $verbose = false;
|
||||
|
||||
/**
|
||||
* Flag for showing only translations, without saving
|
||||
*/
|
||||
private bool $showNew = false;
|
||||
|
||||
private string $languagePath;
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$this->verbose = array_key_exists('verbose', $params);
|
||||
$this->showNew = array_key_exists('show-new', $params);
|
||||
$optionLocale = $params['locale'] ?? null;
|
||||
$optionDir = $params['dir'] ?? null;
|
||||
$currentLocale = Locale::getDefault();
|
||||
$currentDir = APPPATH;
|
||||
$this->languagePath = $currentDir . 'Language';
|
||||
|
||||
if (ENVIRONMENT === 'testing') {
|
||||
$currentDir = SUPPORTPATH . 'Services' . DIRECTORY_SEPARATOR;
|
||||
$this->languagePath = SUPPORTPATH . 'Language';
|
||||
}
|
||||
|
||||
if (is_string($optionLocale)) {
|
||||
if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) {
|
||||
CLI::error(
|
||||
'Error: "' . $optionLocale . '" is not supported. Supported locales: '
|
||||
. implode(', ', config(App::class)->supportedLocales),
|
||||
);
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
$currentLocale = $optionLocale;
|
||||
}
|
||||
|
||||
if (is_string($optionDir)) {
|
||||
$tempCurrentDir = realpath($currentDir . $optionDir);
|
||||
|
||||
if ($tempCurrentDir === false) {
|
||||
CLI::error('Error: Directory must be located in "' . $currentDir . '"');
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
if ($this->isSubDirectory($tempCurrentDir, $this->languagePath)) {
|
||||
CLI::error('Error: Directory "' . $this->languagePath . '" restricted to scan.');
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
$currentDir = $tempCurrentDir;
|
||||
}
|
||||
|
||||
$this->process($currentDir, $currentLocale);
|
||||
|
||||
CLI::write('All operations done!');
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
private function process(string $currentDir, string $currentLocale): void
|
||||
{
|
||||
$tableRows = [];
|
||||
$countNewKeys = 0;
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($currentDir));
|
||||
$files = iterator_to_array($iterator, true);
|
||||
ksort($files);
|
||||
|
||||
[
|
||||
'foundLanguageKeys' => $foundLanguageKeys,
|
||||
'badLanguageKeys' => $badLanguageKeys,
|
||||
'countFiles' => $countFiles,
|
||||
] = $this->findLanguageKeysInFiles($files);
|
||||
|
||||
ksort($foundLanguageKeys);
|
||||
|
||||
$languageDiff = [];
|
||||
$languageFoundGroups = array_unique(array_keys($foundLanguageKeys));
|
||||
|
||||
foreach ($languageFoundGroups as $langFileName) {
|
||||
$languageStoredKeys = [];
|
||||
$languageFilePath = $this->languagePath . DIRECTORY_SEPARATOR . $currentLocale . DIRECTORY_SEPARATOR . $langFileName . '.php';
|
||||
|
||||
if (is_file($languageFilePath)) {
|
||||
// Load old localization
|
||||
$languageStoredKeys = require $languageFilePath;
|
||||
}
|
||||
|
||||
$languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $languageStoredKeys);
|
||||
$countNewKeys += ArrayHelper::recursiveCount($languageDiff);
|
||||
|
||||
if ($this->showNew) {
|
||||
$tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows);
|
||||
} else {
|
||||
$newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys);
|
||||
|
||||
if ($languageDiff !== []) {
|
||||
if (file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys)) === false) {
|
||||
$this->writeIsVerbose('Lang file ' . $langFileName . ' (error write).', 'red');
|
||||
} else {
|
||||
$this->writeIsVerbose('Lang file "' . $langFileName . '" successful updated!', 'green');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->showNew && $tableRows !== []) {
|
||||
sort($tableRows);
|
||||
CLI::table($tableRows, ['File', 'Key']);
|
||||
}
|
||||
|
||||
if (! $this->showNew && $countNewKeys > 0) {
|
||||
CLI::write('Note: You need to run your linting tool to fix coding standards issues.', 'white', 'red');
|
||||
}
|
||||
|
||||
$this->writeIsVerbose('Files found: ' . $countFiles);
|
||||
$this->writeIsVerbose('New translates found: ' . $countNewKeys);
|
||||
$this->writeIsVerbose('Bad translates found: ' . count($badLanguageKeys));
|
||||
|
||||
if ($this->verbose && $badLanguageKeys !== []) {
|
||||
$tableBadRows = [];
|
||||
|
||||
foreach ($badLanguageKeys as $value) {
|
||||
$tableBadRows[] = [$value[1], $value[0]];
|
||||
}
|
||||
|
||||
ArrayHelper::sortValuesByNatural($tableBadRows, 0);
|
||||
|
||||
CLI::table($tableBadRows, ['Bad Key', 'Filepath']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SplFileInfo|string $file
|
||||
*
|
||||
* @return array<string, array>
|
||||
*/
|
||||
private function findTranslationsInFile($file): array
|
||||
{
|
||||
$foundLanguageKeys = [];
|
||||
$badLanguageKeys = [];
|
||||
|
||||
if (is_string($file) && is_file($file)) {
|
||||
$file = new SplFileInfo($file);
|
||||
}
|
||||
|
||||
$fileContent = file_get_contents($file->getRealPath());
|
||||
preg_match_all('/lang\(\'([._a-z0-9\-]+)\'\)/ui', $fileContent, $matches);
|
||||
|
||||
if ($matches[1] === []) {
|
||||
return compact('foundLanguageKeys', 'badLanguageKeys');
|
||||
}
|
||||
|
||||
foreach ($matches[1] as $phraseKey) {
|
||||
$phraseKeys = explode('.', $phraseKey);
|
||||
|
||||
// Language key not have Filename or Lang key
|
||||
if (count($phraseKeys) < 2) {
|
||||
$badLanguageKeys[] = [mb_substr($file->getRealPath(), mb_strlen(ROOTPATH)), $phraseKey];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$languageFileName = array_shift($phraseKeys);
|
||||
$isEmptyNestedArray = ($languageFileName !== '' && $phraseKeys[0] === '')
|
||||
|| ($languageFileName === '' && $phraseKeys[0] !== '')
|
||||
|| ($languageFileName === '' && $phraseKeys[0] === '');
|
||||
|
||||
if ($isEmptyNestedArray) {
|
||||
$badLanguageKeys[] = [mb_substr($file->getRealPath(), mb_strlen(ROOTPATH)), $phraseKey];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($phraseKeys) === 1) {
|
||||
$foundLanguageKeys[$languageFileName][$phraseKeys[0]] = $phraseKey;
|
||||
} else {
|
||||
$childKeys = $this->buildMultiArray($phraseKeys, $phraseKey);
|
||||
|
||||
$foundLanguageKeys[$languageFileName] = array_replace_recursive($foundLanguageKeys[$languageFileName] ?? [], $childKeys);
|
||||
}
|
||||
}
|
||||
|
||||
return compact('foundLanguageKeys', 'badLanguageKeys');
|
||||
}
|
||||
|
||||
private function isIgnoredFile(SplFileInfo $file): bool
|
||||
{
|
||||
if ($file->isDir() || $this->isSubDirectory($file->getRealPath(), $this->languagePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $file->getExtension() !== 'php';
|
||||
}
|
||||
|
||||
private function templateFile(array $language = []): string
|
||||
{
|
||||
if ($language !== []) {
|
||||
$languageArrayString = var_export($language, true);
|
||||
|
||||
$code = <<<PHP
|
||||
<?php
|
||||
|
||||
return {$languageArrayString};
|
||||
|
||||
PHP;
|
||||
|
||||
return $this->replaceArraySyntax($code);
|
||||
}
|
||||
|
||||
return <<<'PHP'
|
||||
<?php
|
||||
|
||||
return [];
|
||||
|
||||
PHP;
|
||||
}
|
||||
|
||||
private function replaceArraySyntax(string $code): string
|
||||
{
|
||||
$tokens = token_get_all($code);
|
||||
$newTokens = $tokens;
|
||||
|
||||
foreach ($tokens as $i => $token) {
|
||||
if (is_array($token)) {
|
||||
[$tokenId, $tokenValue] = $token;
|
||||
|
||||
// Replace "array ("
|
||||
if (
|
||||
$tokenId === T_ARRAY
|
||||
&& $tokens[$i + 1][0] === T_WHITESPACE
|
||||
&& $tokens[$i + 2] === '('
|
||||
) {
|
||||
$newTokens[$i][1] = '[';
|
||||
$newTokens[$i + 1][1] = '';
|
||||
$newTokens[$i + 2] = '';
|
||||
}
|
||||
|
||||
// Replace indent
|
||||
if ($tokenId === T_WHITESPACE && preg_match('/\n([ ]+)/u', $tokenValue, $matches)) {
|
||||
$newTokens[$i][1] = "\n{$matches[1]}{$matches[1]}";
|
||||
}
|
||||
} // Replace ")"
|
||||
elseif ($token === ')') {
|
||||
$newTokens[$i] = ']';
|
||||
}
|
||||
}
|
||||
|
||||
$output = '';
|
||||
|
||||
foreach ($newTokens as $token) {
|
||||
$output .= $token[1] ?? $token;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multidimensional array from another keys
|
||||
*/
|
||||
private function buildMultiArray(array $fromKeys, string $lastArrayValue = ''): array
|
||||
{
|
||||
$newArray = [];
|
||||
$lastIndex = array_pop($fromKeys);
|
||||
$current = &$newArray;
|
||||
|
||||
foreach ($fromKeys as $value) {
|
||||
$current[$value] = [];
|
||||
$current = &$current[$value];
|
||||
}
|
||||
|
||||
$current[$lastIndex] = $lastArrayValue;
|
||||
|
||||
return $newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert multi arrays to specific CLI table rows (flat array)
|
||||
*/
|
||||
private function arrayToTableRows(string $langFileName, array $array): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
foreach ($array as $value) {
|
||||
if (is_array($value)) {
|
||||
$rows = array_merge($rows, $this->arrayToTableRows($langFileName, $value));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$rows[] = [$langFileName, $value];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show details in the console if the flag is set
|
||||
*/
|
||||
private function writeIsVerbose(string $text = '', ?string $foreground = null, ?string $background = null): void
|
||||
{
|
||||
if ($this->verbose) {
|
||||
CLI::write($text, $foreground, $background);
|
||||
}
|
||||
}
|
||||
|
||||
private function isSubDirectory(string $directory, string $rootDirectory): bool
|
||||
{
|
||||
return 0 === strncmp($directory, $rootDirectory, strlen($directory));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<SplFileInfo> $files
|
||||
*
|
||||
* @return array<string, array|int>
|
||||
* @phpstan-return array{'foundLanguageKeys': array<string, array<string, string>>, 'badLanguageKeys': array<int, array<int, string>>, 'countFiles': int}
|
||||
*/
|
||||
private function findLanguageKeysInFiles(array $files): array
|
||||
{
|
||||
$foundLanguageKeys = [];
|
||||
$badLanguageKeys = [];
|
||||
$countFiles = 0;
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($this->isIgnoredFile($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->writeIsVerbose('File found: ' . mb_substr($file->getRealPath(), mb_strlen(APPPATH)));
|
||||
$countFiles++;
|
||||
|
||||
$findInFile = $this->findTranslationsInFile($file);
|
||||
|
||||
$foundLanguageKeys = array_replace_recursive($findInFile['foundLanguageKeys'], $foundLanguageKeys);
|
||||
$badLanguageKeys = array_merge($findInFile['badLanguageKeys'], $badLanguageKeys);
|
||||
}
|
||||
|
||||
return compact('foundLanguageKeys', 'badLanguageKeys', 'countFiles');
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
<?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\Translation;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
use CodeIgniter\Exceptions\LogicException;
|
||||
use Config\App;
|
||||
use ErrorException;
|
||||
use FilesystemIterator;
|
||||
use Locale;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* @see \CodeIgniter\Commands\Translation\LocalizationSyncTest
|
||||
*/
|
||||
class LocalizationSync extends BaseCommand
|
||||
{
|
||||
protected $group = 'Translation';
|
||||
protected $name = 'lang:sync';
|
||||
protected $description = 'Synchronize translation files from one language to another.';
|
||||
protected $usage = 'lang:sync [options]';
|
||||
protected $arguments = [];
|
||||
protected $options = [
|
||||
'--locale' => 'The original locale (en, ru, etc.).',
|
||||
'--target' => 'Target locale (en, ru, etc.).',
|
||||
];
|
||||
private string $languagePath;
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$optionTargetLocale = '';
|
||||
$optionLocale = $params['locale'] ?? Locale::getDefault();
|
||||
$this->languagePath = APPPATH . 'Language';
|
||||
|
||||
if (isset($params['target']) && $params['target'] !== '') {
|
||||
$optionTargetLocale = $params['target'];
|
||||
}
|
||||
|
||||
if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) {
|
||||
CLI::error(
|
||||
'Error: "' . $optionLocale . '" is not supported. Supported locales: '
|
||||
. implode(', ', config(App::class)->supportedLocales),
|
||||
);
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
if ($optionTargetLocale === '') {
|
||||
CLI::error(
|
||||
'Error: "--target" is not configured. Supported locales: '
|
||||
. implode(', ', config(App::class)->supportedLocales),
|
||||
);
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
if (! in_array($optionTargetLocale, config(App::class)->supportedLocales, true)) {
|
||||
CLI::error(
|
||||
'Error: "' . $optionTargetLocale . '" is not supported. Supported locales: '
|
||||
. implode(', ', config(App::class)->supportedLocales),
|
||||
);
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
if ($optionTargetLocale === $optionLocale) {
|
||||
CLI::error(
|
||||
'Error: You cannot have the same values for "--target" and "--locale".',
|
||||
);
|
||||
|
||||
return EXIT_USER_INPUT;
|
||||
}
|
||||
|
||||
if (ENVIRONMENT === 'testing') {
|
||||
$this->languagePath = SUPPORTPATH . 'Language';
|
||||
}
|
||||
|
||||
if ($this->process($optionLocale, $optionTargetLocale) === EXIT_ERROR) {
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
CLI::write('All operations done!');
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
private function process(string $originalLocale, string $targetLocale): int
|
||||
{
|
||||
$originalLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $originalLocale;
|
||||
$targetLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $targetLocale;
|
||||
|
||||
if (! is_dir($originalLocaleDir)) {
|
||||
CLI::error(
|
||||
'Error: The "' . clean_path($originalLocaleDir) . '" directory was not found.',
|
||||
);
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
// Unifying the error - mkdir() may cause an exception.
|
||||
try {
|
||||
if (! is_dir($targetLocaleDir) && ! mkdir($targetLocaleDir, 0775)) {
|
||||
throw new ErrorException();
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
CLI::error(
|
||||
'Error: The target directory "' . clean_path($targetLocaleDir) . '" cannot be accessed.',
|
||||
);
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$originalLocaleDir,
|
||||
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array<non-empty-string, SplFileInfo> $files
|
||||
*/
|
||||
$files = iterator_to_array($iterator, true);
|
||||
ksort($files);
|
||||
|
||||
foreach ($files as $originalLanguageFile) {
|
||||
if ($originalLanguageFile->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetLanguageFile = $targetLocaleDir . DIRECTORY_SEPARATOR . $originalLanguageFile->getFilename();
|
||||
|
||||
$targetLanguageKeys = [];
|
||||
$originalLanguageKeys = include $originalLanguageFile;
|
||||
|
||||
if (is_file($targetLanguageFile)) {
|
||||
$targetLanguageKeys = include $targetLanguageFile;
|
||||
}
|
||||
|
||||
$targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php'));
|
||||
|
||||
$content = "<?php\n\nreturn " . var_export($targetLanguageKeys, true) . ";\n";
|
||||
file_put_contents($targetLanguageFile, $content);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string,mixed>|string|null> $originalLanguageKeys
|
||||
* @param array<string, array<string,mixed>|string|null> $targetLanguageKeys
|
||||
*
|
||||
* @return array<string, array<string,mixed>|string|null>
|
||||
*/
|
||||
private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLanguageKeys, string $prefix = ''): array
|
||||
{
|
||||
$mergedLanguageKeys = [];
|
||||
|
||||
foreach ($originalLanguageKeys as $key => $value) {
|
||||
$placeholderValue = $prefix !== '' ? $prefix . '.' . $key : $key;
|
||||
|
||||
if (is_string($value)) {
|
||||
// Keep the old value
|
||||
// TODO: The value type may not match the original one
|
||||
if (array_key_exists($key, $targetLanguageKeys)) {
|
||||
$mergedLanguageKeys[$key] = $targetLanguageKeys[$key];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set new key with placeholder
|
||||
$mergedLanguageKeys[$key] = $placeholderValue;
|
||||
} elseif (is_array($value)) {
|
||||
if (! array_key_exists($key, $targetLanguageKeys)) {
|
||||
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, [], $placeholderValue);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue);
|
||||
} else {
|
||||
throw new LogicException('Value for the key "' . $placeholderValue . '" is of the wrong type. Only "array" or "string" is allowed.');
|
||||
}
|
||||
}
|
||||
|
||||
return $mergedLanguageKeys;
|
||||
}
|
||||
}
|
||||
+156
@@ -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 = [];
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?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',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
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 EXIT_ERROR;
|
||||
}
|
||||
|
||||
$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 EXIT_ERROR;
|
||||
}
|
||||
|
||||
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 EXIT_ERROR;
|
||||
}
|
||||
|
||||
if (! $this->writeNewEnvironmentToEnvFile($env)) {
|
||||
CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red');
|
||||
CLI::newLine();
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
<?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)
|
||||
{
|
||||
if (! $this->checkParams($params)) {
|
||||
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;
|
||||
}
|
||||
|
||||
$this->showTable($filterCollector, $filters, $method, $route);
|
||||
$this->showFilterClasses($filterCollector, $method, $route);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
private function checkParams(array $params): bool
|
||||
{
|
||||
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 false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{before: list<string>, after: list<string>} $filters
|
||||
*/
|
||||
private function showTable(
|
||||
FilterCollector $filterCollector,
|
||||
array $filters,
|
||||
string $method,
|
||||
string $route,
|
||||
): void {
|
||||
$thead = [
|
||||
'Method',
|
||||
'Route',
|
||||
'Before Filters',
|
||||
'After Filters',
|
||||
];
|
||||
|
||||
$required = $filterCollector->getRequiredFilters();
|
||||
|
||||
$coloredRequired = $this->colorItems($required);
|
||||
|
||||
$before = array_merge($coloredRequired['before'], $filters['before']);
|
||||
$after = array_merge($filters['after'], $coloredRequired['after']);
|
||||
|
||||
$tbody = [];
|
||||
$tbody[] = [
|
||||
strtoupper($method),
|
||||
$route,
|
||||
implode(' ', $before),
|
||||
implode(' ', $after),
|
||||
];
|
||||
|
||||
CLI::table($tbody, $thead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Color all elements of the array.
|
||||
*
|
||||
* @param array<array-key, mixed> $array
|
||||
*
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
private function colorItems(array $array): array
|
||||
{
|
||||
return array_map(function ($item): array|string {
|
||||
if (is_array($item)) {
|
||||
return $this->colorItems($item);
|
||||
}
|
||||
|
||||
return CLI::color($item, 'yellow');
|
||||
}, $array);
|
||||
}
|
||||
|
||||
private function showFilterClasses(
|
||||
FilterCollector $filterCollector,
|
||||
string $method,
|
||||
string $route,
|
||||
): void {
|
||||
$requiredFilterClasses = $filterCollector->getRequiredFilterClasses();
|
||||
$filterClasses = $filterCollector->getClasses($method, $route);
|
||||
|
||||
$coloredRequiredFilterClasses = $this->colorItems($requiredFilterClasses);
|
||||
|
||||
$classList = [
|
||||
'before' => array_merge($coloredRequiredFilterClasses['before'], $filterClasses['before']),
|
||||
'after' => array_merge($filterClasses['after'], $coloredRequiredFilterClasses['after']),
|
||||
];
|
||||
|
||||
foreach ($classList as $position => $classes) {
|
||||
CLI::write(ucfirst($position) . ' Filter Classes:', 'cyan');
|
||||
CLI::write(implode(' → ', $classes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = mb_strlen($string);
|
||||
|
||||
if ($length > $max) {
|
||||
return mb_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\Exceptions\RuntimeException;
|
||||
use CodeIgniter\Publisher\Publisher;
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
try {
|
||||
$this->enableCaching();
|
||||
$this->clearCache();
|
||||
$this->removeDevPackages();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
} catch (RuntimeException) {
|
||||
CLI::error('The "spark optimize" failed.');
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
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,96 @@
|
||||
<?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\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 in production environment.';
|
||||
|
||||
/**
|
||||
* The Command's usage
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage = 'phpini:check';
|
||||
|
||||
/**
|
||||
* The Command's arguments
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [
|
||||
'opcache' => 'Check detail opcache values in production environment.',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Command's options
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
if (isset($params[0]) && ! in_array($params[0], array_keys($this->arguments), true)) {
|
||||
CLI::error('You must specify a correct argument.');
|
||||
CLI::write(' Usage: ' . $this->usage);
|
||||
CLI::write(' Example: phpini:check opcache');
|
||||
CLI::write('Arguments:');
|
||||
|
||||
$length = max(array_map(strlen(...), array_keys($this->arguments)));
|
||||
|
||||
foreach ($this->arguments as $argument => $description) {
|
||||
CLI::write(CLI::color($this->setPad($argument, $length, 2, 2), 'green') . $description);
|
||||
}
|
||||
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$argument = $params[0] ?? null;
|
||||
|
||||
CheckPhpIni::run(argument: $argument);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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 = [
|
||||
'--namespace' => 'The namespace from which to search for files to publish. By default, all namespaces are analysed.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays the help for the spark cli script itself.
|
||||
*/
|
||||
public function run(array $params)
|
||||
{
|
||||
$directory = $params[0] ?? 'Publishers';
|
||||
$namespace = $params['namespace'] ?? '';
|
||||
|
||||
if ([] === $publishers = Publisher::discover($directory, $namespace)) {
|
||||
if ($namespace === '') {
|
||||
CLI::write(lang('Publisher.publishMissing', [$directory]));
|
||||
} else {
|
||||
CLI::write(lang('Publisher.publishMissingNamespace', [$directory, $namespace]));
|
||||
}
|
||||
|
||||
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,224 @@
|
||||
<?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 !== null) {
|
||||
$request = service('request');
|
||||
$_SERVER = $request->getServer();
|
||||
$_SERVER['HTTP_HOST'] = $host;
|
||||
$request->setGlobal('server', $_SERVER);
|
||||
}
|
||||
|
||||
$collection = service('routes')->loadRoutes();
|
||||
|
||||
// Reset HTTP_HOST
|
||||
if ($host !== null) {
|
||||
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.
|
||||
$routingConfig = config(Routing::class);
|
||||
|
||||
if ($routingConfig instanceof Routing) {
|
||||
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): int => strcmp($handler1[3], $handler2[3]));
|
||||
}
|
||||
|
||||
if ($host !== null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
<?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 readonly 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']),
|
||||
'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;
|
||||
}
|
||||
}
|
||||
+243
@@ -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 !== '' && $routeWithoutController !== '0' ? $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;
|
||||
}
|
||||
}
|
||||
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+171
@@ -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 !== '' && $routeWithoutController !== '0' ? $routeWithoutController : '/';
|
||||
|
||||
return [[
|
||||
'route' => $routeWithoutController,
|
||||
'handler' => '\\' . $classname . '::' . $methodName,
|
||||
]];
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?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\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 alias/classname:args
|
||||
*/
|
||||
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 = service('incomingrequest', null, false);
|
||||
$request->setMethod($method);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->find($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns filter classes 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 classname:args
|
||||
*/
|
||||
public function getClasses(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 = service('incomingrequest', null, false);
|
||||
$request->setMethod($method);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->findClasses($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filters
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of aliases
|
||||
*/
|
||||
public function getRequiredFilters(): array
|
||||
{
|
||||
$request = service('incomingrequest', null, false);
|
||||
$request->setMethod(Method::GET);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->getRequiredFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filter class list
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of classnames
|
||||
*/
|
||||
public function getRequiredFilterClasses(): array
|
||||
{
|
||||
$request = service('incomingrequest', null, false);
|
||||
$request->setMethod(Method::GET);
|
||||
|
||||
$router = $this->createRouter($request);
|
||||
$filters = $this->createFilters($request);
|
||||
|
||||
$finder = new FilterFinder($router, $filters);
|
||||
|
||||
return $finder->getRequiredFilterClasses();
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
<?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 alias/classname:args
|
||||
*/
|
||||
public function find(string $uri): array
|
||||
{
|
||||
$this->filters->reset();
|
||||
|
||||
try {
|
||||
// Add route filters
|
||||
$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>'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri URI path to find filters for
|
||||
*
|
||||
* @return array{before: list<string>, after: list<string>} array of classname:args
|
||||
*/
|
||||
public function findClasses(string $uri): array
|
||||
{
|
||||
$this->filters->reset();
|
||||
|
||||
try {
|
||||
// Add route filters
|
||||
$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);
|
||||
|
||||
$filterClassList = $this->filters->getFiltersClass();
|
||||
|
||||
$filterClasses = [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
|
||||
foreach ($filterClassList['before'] as $classInfo) {
|
||||
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
|
||||
: $classInfo[0] . ':' . implode(',', $classInfo[1]);
|
||||
|
||||
$filterClasses['before'][] = $classWithArguments;
|
||||
}
|
||||
|
||||
foreach ($filterClassList['after'] as $classInfo) {
|
||||
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
|
||||
: $classInfo[0] . ':' . implode(',', $classInfo[1]);
|
||||
|
||||
$filterClasses['after'][] = $classWithArguments;
|
||||
}
|
||||
|
||||
return $filterClasses;
|
||||
} catch (RedirectException) {
|
||||
return [
|
||||
'before' => [],
|
||||
'after' => [],
|
||||
];
|
||||
} catch (BadRequestException|PageNotFoundException) {
|
||||
return [
|
||||
'before' => ['<unknown>'],
|
||||
'after' => ['<unknown>'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filters
|
||||
*
|
||||
* @return array{before: list<string>, after:list<string>} array of aliases
|
||||
*/
|
||||
public function getRequiredFilters(): array
|
||||
{
|
||||
[$requiredBefore] = $this->filters->getRequiredFilters('before');
|
||||
[$requiredAfter] = $this->filters->getRequiredFilters('after');
|
||||
|
||||
return [
|
||||
'before' => $requiredBefore,
|
||||
'after' => $requiredAfter,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Required Filter classes
|
||||
*
|
||||
* @return array{before: list<string>, after:list<string>}
|
||||
*/
|
||||
public function getRequiredFilterClasses(): array
|
||||
{
|
||||
$before = $this->filters->getRequiredClasses('before');
|
||||
$after = $this->filters->getRequiredClasses('after');
|
||||
|
||||
$requiredBefore = [];
|
||||
$requiredAfter = [];
|
||||
|
||||
foreach ($before as $classInfo) {
|
||||
$requiredBefore[] = $classInfo[0];
|
||||
}
|
||||
|
||||
foreach ($after as $classInfo) {
|
||||
$requiredAfter[] = $classInfo[0];
|
||||
}
|
||||
|
||||
return [
|
||||
'before' => $requiredBefore,
|
||||
'after' => $requiredAfter,
|
||||
];
|
||||
}
|
||||
}
|
||||
+73
@@ -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);
|
||||
}
|
||||
}
|
||||
+1263
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
<?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;
|
||||
|
||||
use FilesystemIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
* This class is used by Composer during installs and updates
|
||||
* to move files to locations within the system folder so that end-users
|
||||
* do not need to use Composer to install a package, but can simply
|
||||
* download.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ComposerScripts
|
||||
{
|
||||
/**
|
||||
* Path to the ThirdParty directory.
|
||||
*/
|
||||
private static string $path = __DIR__ . '/ThirdParty/';
|
||||
|
||||
/**
|
||||
* Direct dependencies of CodeIgniter to copy
|
||||
* contents to `system/ThirdParty/`.
|
||||
*
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
private static array $dependencies = [
|
||||
'kint-src' => [
|
||||
'license' => __DIR__ . '/../vendor/kint-php/kint/LICENSE',
|
||||
'from' => __DIR__ . '/../vendor/kint-php/kint/src/',
|
||||
'to' => __DIR__ . '/ThirdParty/Kint/',
|
||||
],
|
||||
'kint-resources' => [
|
||||
'from' => __DIR__ . '/../vendor/kint-php/kint/resources/',
|
||||
'to' => __DIR__ . '/ThirdParty/Kint/resources/',
|
||||
],
|
||||
'escaper' => [
|
||||
'license' => __DIR__ . '/../vendor/laminas/laminas-escaper/LICENSE.md',
|
||||
'from' => __DIR__ . '/../vendor/laminas/laminas-escaper/src/',
|
||||
'to' => __DIR__ . '/ThirdParty/Escaper/',
|
||||
],
|
||||
'psr-log' => [
|
||||
'license' => __DIR__ . '/../vendor/psr/log/LICENSE',
|
||||
'from' => __DIR__ . '/../vendor/psr/log/src/',
|
||||
'to' => __DIR__ . '/ThirdParty/PSR/Log/',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This static method is called by Composer after every update event,
|
||||
* i.e., `composer install`, `composer update`, `composer remove`.
|
||||
*/
|
||||
public static function postUpdate(): void
|
||||
{
|
||||
self::recursiveDelete(self::$path);
|
||||
|
||||
foreach (self::$dependencies as $key => $dependency) {
|
||||
// Kint may be removed.
|
||||
if (! is_dir($dependency['from']) && str_starts_with($key, 'kint')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::recursiveMirror($dependency['from'], $dependency['to']);
|
||||
|
||||
if (isset($dependency['license'])) {
|
||||
$license = basename($dependency['license']);
|
||||
copy($dependency['license'], $dependency['to'] . '/' . $license);
|
||||
}
|
||||
}
|
||||
|
||||
self::copyKintInitFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively remove the contents of the previous `system/ThirdParty`.
|
||||
*/
|
||||
private static function recursiveDelete(string $directory): void
|
||||
{
|
||||
if (! is_dir($directory)) {
|
||||
echo sprintf('Cannot recursively delete "%s" as it does not exist.', $directory) . PHP_EOL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach (new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(rtrim($directory, '\\/'), FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST,
|
||||
) as $file) {
|
||||
$path = $file->getPathname();
|
||||
|
||||
if ($file->isDir()) {
|
||||
@rmdir($path);
|
||||
} else {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copy the files and directories of the origin directory
|
||||
* into the target directory, i.e. "mirror" its contents.
|
||||
*/
|
||||
private static function recursiveMirror(string $originDir, string $targetDir): void
|
||||
{
|
||||
$originDir = rtrim($originDir, '\\/');
|
||||
$targetDir = rtrim($targetDir, '\\/');
|
||||
|
||||
if (! is_dir($originDir)) {
|
||||
echo sprintf('The origin directory "%s" was not found.', $originDir);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (is_dir($targetDir)) {
|
||||
echo sprintf('The target directory "%s" is existing. Run %s::recursiveDelete(\'%s\') first.', $targetDir, self::class, $targetDir);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (! @mkdir($targetDir, 0755, true)) {
|
||||
echo sprintf('Cannot create the target directory: "%s"', $targetDir) . PHP_EOL;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$dirLen = strlen($originDir);
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach (new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($originDir, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST,
|
||||
) as $file) {
|
||||
$origin = $file->getPathname();
|
||||
$target = $targetDir . substr($origin, $dirLen);
|
||||
|
||||
if ($file->isDir()) {
|
||||
@mkdir($target, 0755);
|
||||
} else {
|
||||
@copy($origin, $target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Kint's init files into `system/ThirdParty/Kint/`
|
||||
*/
|
||||
private static function copyKintInitFiles(): void
|
||||
{
|
||||
$originDir = self::$dependencies['kint-src']['from'] . '../';
|
||||
$targetDir = self::$dependencies['kint-src']['to'];
|
||||
|
||||
foreach (['init.php', 'init_helpers.php'] as $kintInit) {
|
||||
@copy($originDir . $kintInit, $targetDir . $kintInit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?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\Config;
|
||||
|
||||
use Laminas\Escaper\Escaper;
|
||||
use Laminas\Escaper\Exception\ExceptionInterface;
|
||||
use Laminas\Escaper\Exception\InvalidArgumentException as EscaperInvalidArgumentException;
|
||||
use Laminas\Escaper\Exception\RuntimeException;
|
||||
use Psr\Log\AbstractLogger;
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LoggerTrait;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* AUTOLOADER CONFIGURATION
|
||||
*
|
||||
* This file defines the namespaces and class maps so the Autoloader
|
||||
* can find the files as needed.
|
||||
*/
|
||||
class AutoloadConfig
|
||||
{
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Namespaces
|
||||
* -------------------------------------------------------------------
|
||||
* This maps the locations of any namespaces in your application to
|
||||
* their location on the file system. These are used by the autoloader
|
||||
* to locate files the first time they have been instantiated.
|
||||
*
|
||||
* The '/app' and '/system' directories are already mapped for you.
|
||||
* you may change the name of the 'App' namespace if you wish,
|
||||
* but this should be done prior to creating any namespaced classes,
|
||||
* else you will need to modify all of those classes for this to work.
|
||||
*
|
||||
* @var array<string, list<string>|string>
|
||||
*/
|
||||
public $psr4 = [];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Class Map
|
||||
* -------------------------------------------------------------------
|
||||
* The class map provides a map of class names and their exact
|
||||
* location on the drive. Classes loaded in this manner will have
|
||||
* slightly faster performance because they will not have to be
|
||||
* searched for within one or more directories as they would if they
|
||||
* were being autoloaded through a namespace.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $classmap = [];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Files
|
||||
* -------------------------------------------------------------------
|
||||
* The files array provides a list of paths to __non-class__ files
|
||||
* that will be autoloaded. This can be useful for bootstrap operations
|
||||
* or for loading functions.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public $files = [];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Namespaces
|
||||
* -------------------------------------------------------------------
|
||||
* This maps the locations of any namespaces in your application to
|
||||
* their location on the file system. These are used by the autoloader
|
||||
* to locate files the first time they have been instantiated.
|
||||
*
|
||||
* Do not change the name of the CodeIgniter namespace or your application
|
||||
* will break.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $corePsr4 = [
|
||||
'CodeIgniter' => SYSTEMPATH,
|
||||
'Config' => APPPATH . 'Config',
|
||||
];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Class Map
|
||||
* -------------------------------------------------------------------
|
||||
* The class map provides a map of class names and their exact
|
||||
* location on the drive. Classes loaded in this manner will have
|
||||
* slightly faster performance because they will not have to be
|
||||
* searched for within one or more directories as they would if they
|
||||
* were being autoloaded through a namespace.
|
||||
*
|
||||
* @var array<class-string, string>
|
||||
*/
|
||||
protected $coreClassmap = [
|
||||
AbstractLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php',
|
||||
InvalidArgumentException::class => SYSTEMPATH . 'ThirdParty/PSR/Log/InvalidArgumentException.php',
|
||||
LoggerAwareInterface::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareInterface.php',
|
||||
LoggerAwareTrait::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareTrait.php',
|
||||
LoggerInterface::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerInterface.php',
|
||||
LoggerTrait::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerTrait.php',
|
||||
LogLevel::class => SYSTEMPATH . 'ThirdParty/PSR/Log/LogLevel.php',
|
||||
NullLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/NullLogger.php',
|
||||
ExceptionInterface::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/ExceptionInterface.php',
|
||||
EscaperInvalidArgumentException::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/InvalidArgumentException.php',
|
||||
RuntimeException::class => SYSTEMPATH . 'ThirdParty/Escaper/Exception/RuntimeException.php',
|
||||
Escaper::class => SYSTEMPATH . 'ThirdParty/Escaper/Escaper.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Core Files
|
||||
* -------------------------------------------------------------------
|
||||
* List of files from the framework to be autoloaded early.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $coreFiles = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Merge the built-in and developer-configured psr4 and classmap,
|
||||
* with preference to the developer ones.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing') {
|
||||
$this->psr4['Tests\Support'] = SUPPORTPATH;
|
||||
$this->classmap['CodeIgniter\Log\TestLogger'] = SYSTEMPATH . 'Test/TestLogger.php';
|
||||
$this->classmap['CIDatabaseTestCase'] = SYSTEMPATH . 'Test/CIDatabaseTestCase.php';
|
||||
}
|
||||
|
||||
$this->psr4 = array_merge($this->corePsr4, $this->psr4);
|
||||
$this->classmap = array_merge($this->coreClassmap, $this->classmap);
|
||||
$this->files = [...$this->coreFiles, ...$this->files];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
use CodeIgniter\Exceptions\ConfigException;
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
use Config\Encryption;
|
||||
use Config\Modules;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Class BaseConfig
|
||||
*
|
||||
* Not intended to be used on its own, this class will attempt to
|
||||
* automatically populate the child class' properties with values
|
||||
* from the environment.
|
||||
*
|
||||
* These can be set within the .env file.
|
||||
*
|
||||
* @phpstan-consistent-constructor
|
||||
* @see \CodeIgniter\Config\BaseConfigTest
|
||||
*/
|
||||
class BaseConfig
|
||||
{
|
||||
/**
|
||||
* An optional array of classes that will act as Registrars
|
||||
* for rapidly setting config class properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $registrars = [];
|
||||
|
||||
/**
|
||||
* Whether to override properties by Env vars and Registrars.
|
||||
*/
|
||||
public static bool $override = true;
|
||||
|
||||
/**
|
||||
* Has module discovery completed?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $didDiscovery = false;
|
||||
|
||||
/**
|
||||
* Is module discovery running or not?
|
||||
*/
|
||||
protected static bool $discovering = false;
|
||||
|
||||
/**
|
||||
* The processing Registrar file for error message.
|
||||
*/
|
||||
protected static string $registrarFile = '';
|
||||
|
||||
/**
|
||||
* The modules configuration.
|
||||
*
|
||||
* @var Modules|null
|
||||
*/
|
||||
protected static $moduleConfig;
|
||||
|
||||
public static function __set_state(array $array)
|
||||
{
|
||||
static::$override = false;
|
||||
$obj = new static();
|
||||
static::$override = true;
|
||||
|
||||
$properties = array_keys(get_object_vars($obj));
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$obj->{$property} = $array[$property];
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For testing purposes only.
|
||||
* @testTag
|
||||
*/
|
||||
public static function setModules(Modules $modules): void
|
||||
{
|
||||
static::$moduleConfig = $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For testing purposes only.
|
||||
* @testTag
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
static::$registrars = [];
|
||||
static::$override = true;
|
||||
static::$didDiscovery = false;
|
||||
static::$moduleConfig = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to get environment variables with names
|
||||
* that match the properties of the child class.
|
||||
*
|
||||
* The "shortPrefix" is the lowercase-only config class name.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
static::$moduleConfig ??= new Modules();
|
||||
|
||||
if (! static::$override) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->registerProperties();
|
||||
|
||||
$properties = array_keys(get_object_vars($this));
|
||||
$prefix = static::class;
|
||||
$slashAt = strrpos($prefix, '\\');
|
||||
$shortPrefix = strtolower(substr($prefix, $slashAt === false ? 0 : $slashAt + 1));
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$this->initEnvValue($this->{$property}, $property, $prefix, $shortPrefix);
|
||||
|
||||
if ($this instanceof Encryption && $property === 'key') {
|
||||
if (str_starts_with($this->{$property}, 'hex2bin:')) {
|
||||
// Handle hex2bin prefix
|
||||
$this->{$property} = hex2bin(substr($this->{$property}, 8));
|
||||
} elseif (str_starts_with($this->{$property}, 'base64:')) {
|
||||
// Handle base64 prefix
|
||||
$this->{$property} = base64_decode(substr($this->{$property}, 7), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization an environment-specific configuration setting
|
||||
*
|
||||
* @param array|bool|float|int|string|null $property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix)
|
||||
{
|
||||
if (is_array($property)) {
|
||||
foreach (array_keys($property) as $key) {
|
||||
$this->initEnvValue($property[$key], "{$name}.{$key}", $prefix, $shortPrefix);
|
||||
}
|
||||
} elseif (($value = $this->getEnvValue($name, $prefix, $shortPrefix)) !== false && $value !== null) {
|
||||
if ($value === 'false') {
|
||||
$value = false;
|
||||
} elseif ($value === 'true') {
|
||||
$value = true;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
$property = $value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = trim($value, '\'"');
|
||||
|
||||
if (is_int($property)) {
|
||||
$value = (int) $value;
|
||||
} elseif (is_float($property)) {
|
||||
$value = (float) $value;
|
||||
}
|
||||
|
||||
// If the default value of the property is `null` and the type is not
|
||||
// `string`, TypeError will happen.
|
||||
// So cannot set `declare(strict_types=1)` in this file.
|
||||
$property = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an environment-specific configuration setting
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getEnvValue(string $property, string $prefix, string $shortPrefix)
|
||||
{
|
||||
$shortPrefix = ltrim($shortPrefix, '\\');
|
||||
$underscoreProperty = str_replace('.', '_', $property);
|
||||
|
||||
switch (true) {
|
||||
case array_key_exists("{$shortPrefix}.{$property}", $_ENV):
|
||||
return $_ENV["{$shortPrefix}.{$property}"];
|
||||
|
||||
case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_ENV):
|
||||
return $_ENV["{$shortPrefix}_{$underscoreProperty}"];
|
||||
|
||||
case array_key_exists("{$shortPrefix}.{$property}", $_SERVER):
|
||||
return $_SERVER["{$shortPrefix}.{$property}"];
|
||||
|
||||
case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_SERVER):
|
||||
return $_SERVER["{$shortPrefix}_{$underscoreProperty}"];
|
||||
|
||||
case array_key_exists("{$prefix}.{$property}", $_ENV):
|
||||
return $_ENV["{$prefix}.{$property}"];
|
||||
|
||||
case array_key_exists("{$prefix}_{$underscoreProperty}", $_ENV):
|
||||
return $_ENV["{$prefix}_{$underscoreProperty}"];
|
||||
|
||||
case array_key_exists("{$prefix}.{$property}", $_SERVER):
|
||||
return $_SERVER["{$prefix}.{$property}"];
|
||||
|
||||
case array_key_exists("{$prefix}_{$underscoreProperty}", $_SERVER):
|
||||
return $_SERVER["{$prefix}_{$underscoreProperty}"];
|
||||
|
||||
default:
|
||||
$value = getenv("{$shortPrefix}.{$property}");
|
||||
$value = $value === false ? getenv("{$shortPrefix}_{$underscoreProperty}") : $value;
|
||||
$value = $value === false ? getenv("{$prefix}.{$property}") : $value;
|
||||
$value = $value === false ? getenv("{$prefix}_{$underscoreProperty}") : $value;
|
||||
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides external libraries a simple way to register one or more
|
||||
* options into a config file.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
protected function registerProperties()
|
||||
{
|
||||
if (! static::$moduleConfig->shouldDiscover('registrars')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! static::$didDiscovery) {
|
||||
// Discovery must be completed before the first instantiation of any Config class.
|
||||
if (static::$discovering) {
|
||||
throw new ConfigException(
|
||||
'During Auto-Discovery of Registrars,'
|
||||
. ' "' . static::class . '" executes Auto-Discovery again.'
|
||||
. ' "' . clean_path(static::$registrarFile) . '" seems to have bad code.',
|
||||
);
|
||||
}
|
||||
|
||||
static::$discovering = true;
|
||||
|
||||
/** @var FileLocatorInterface */
|
||||
$locator = service('locator');
|
||||
$registrarsFiles = $locator->search('Config/Registrar.php');
|
||||
|
||||
foreach ($registrarsFiles as $file) {
|
||||
// Saves the file for error message.
|
||||
static::$registrarFile = $file;
|
||||
|
||||
$className = $locator->findQualifiedNameFromPath($file);
|
||||
|
||||
if ($className === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
static::$registrars[] = new $className();
|
||||
}
|
||||
|
||||
static::$didDiscovery = true;
|
||||
static::$discovering = false;
|
||||
}
|
||||
|
||||
$shortName = (new ReflectionClass($this))->getShortName();
|
||||
|
||||
// Check the registrar class for a method named after this class' shortName
|
||||
foreach (static::$registrars as $callable) {
|
||||
// ignore non-applicable registrars
|
||||
if (! method_exists($callable, $shortName)) {
|
||||
continue; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$properties = $callable::$shortName();
|
||||
|
||||
if (! is_array($properties)) {
|
||||
throw new RuntimeException('Registrars must return an array of properties and their values.');
|
||||
}
|
||||
|
||||
foreach ($properties as $property => $value) {
|
||||
if (isset($this->{$property}) && is_array($this->{$property}) && is_array($value)) {
|
||||
$this->{$property} = array_merge($this->{$property}, $value);
|
||||
} else {
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Autoloader\Autoloader;
|
||||
use CodeIgniter\Autoloader\FileLocator;
|
||||
use CodeIgniter\Autoloader\FileLocatorCached;
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Cache\ResponseCache;
|
||||
use CodeIgniter\CLI\Commands;
|
||||
use CodeIgniter\CodeIgniter;
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Database\MigrationRunner;
|
||||
use CodeIgniter\Debug\Exceptions;
|
||||
use CodeIgniter\Debug\Iterator;
|
||||
use CodeIgniter\Debug\Timer;
|
||||
use CodeIgniter\Debug\Toolbar;
|
||||
use CodeIgniter\Email\Email;
|
||||
use CodeIgniter\Encryption\EncrypterInterface;
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use CodeIgniter\Filters\Filters;
|
||||
use CodeIgniter\Format\Format;
|
||||
use CodeIgniter\Honeypot\Honeypot;
|
||||
use CodeIgniter\HTTP\CLIRequest;
|
||||
use CodeIgniter\HTTP\ContentSecurityPolicy;
|
||||
use CodeIgniter\HTTP\CURLRequest;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\Negotiate;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\HTTP\SiteURIFactory;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\Images\Handlers\BaseHandler;
|
||||
use CodeIgniter\Language\Language;
|
||||
use CodeIgniter\Log\Logger;
|
||||
use CodeIgniter\Pager\Pager;
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
use CodeIgniter\Router\RouteCollectionInterface;
|
||||
use CodeIgniter\Router\Router;
|
||||
use CodeIgniter\Security\Security;
|
||||
use CodeIgniter\Session\Session;
|
||||
use CodeIgniter\Superglobals;
|
||||
use CodeIgniter\Throttle\Throttler;
|
||||
use CodeIgniter\Typography\Typography;
|
||||
use CodeIgniter\Validation\ValidationInterface;
|
||||
use CodeIgniter\View\Cell;
|
||||
use CodeIgniter\View\Parser;
|
||||
use CodeIgniter\View\RendererInterface;
|
||||
use CodeIgniter\View\View;
|
||||
use Config\App;
|
||||
use Config\Autoload;
|
||||
use Config\Cache;
|
||||
use Config\ContentSecurityPolicy as CSPConfig;
|
||||
use Config\Encryption;
|
||||
use Config\Exceptions as ConfigExceptions;
|
||||
use Config\Filters as ConfigFilters;
|
||||
use Config\Format as ConfigFormat;
|
||||
use Config\Honeypot as ConfigHoneyPot;
|
||||
use Config\Images;
|
||||
use Config\Migrations;
|
||||
use Config\Modules;
|
||||
use Config\Optimize;
|
||||
use Config\Pager as ConfigPager;
|
||||
use Config\Services as AppServices;
|
||||
use Config\Session as ConfigSession;
|
||||
use Config\Toolbar as ConfigToolbar;
|
||||
use Config\Validation as ConfigValidation;
|
||||
use Config\View as ConfigView;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
*
|
||||
* Services are simply other classes/libraries that the system uses
|
||||
* to do its job. This is used by CodeIgniter to allow the core of the
|
||||
* framework to be swapped out easily without affecting the usage within
|
||||
* the rest of your application.
|
||||
*
|
||||
* This is used in place of a Dependency Injection container primarily
|
||||
* due to its simplicity, which allows a better long-term maintenance
|
||||
* of the applications built on top of CodeIgniter. A bonus side-effect
|
||||
* is that IDEs are able to determine what class you are calling
|
||||
* whereas with DI Containers there usually isn't a way for them to do this.
|
||||
*
|
||||
* Warning: To allow overrides by service providers do not use static calls,
|
||||
* instead call out to \Config\Services (imported as AppServices).
|
||||
*
|
||||
* @see http://blog.ircmaxell.com/2015/11/simple-easy-risk-and-change.html
|
||||
* @see http://www.infoq.com/presentations/Simple-Made-Easy
|
||||
*
|
||||
* @method static CacheInterface cache(Cache $config = null, $getShared = true)
|
||||
* @method static CLIRequest clirequest(App $config = null, $getShared = true)
|
||||
* @method static CodeIgniter codeigniter(App $config = null, $getShared = true)
|
||||
* @method static Commands commands($getShared = true)
|
||||
* @method static void createRequest(App $config, bool $isCli = false)
|
||||
* @method static ContentSecurityPolicy csp(CSPConfig $config = null, $getShared = true)
|
||||
* @method static CURLRequest curlrequest($options = [], ResponseInterface $response = null, App $config = null, $getShared = true)
|
||||
* @method static Email email($config = null, $getShared = true)
|
||||
* @method static EncrypterInterface encrypter(Encryption $config = null, $getShared = false)
|
||||
* @method static Exceptions exceptions(ConfigExceptions $config = null, $getShared = true)
|
||||
* @method static Filters filters(ConfigFilters $config = null, $getShared = true)
|
||||
* @method static Format format(ConfigFormat $config = null, $getShared = true)
|
||||
* @method static Honeypot honeypot(ConfigHoneyPot $config = null, $getShared = true)
|
||||
* @method static BaseHandler image($handler = null, Images $config = null, $getShared = true)
|
||||
* @method static IncomingRequest incomingrequest(?App $config = null, bool $getShared = true)
|
||||
* @method static Iterator iterator($getShared = true)
|
||||
* @method static Language language($locale = null, $getShared = true)
|
||||
* @method static Logger logger($getShared = true)
|
||||
* @method static MigrationRunner migrations(Migrations $config = null, ConnectionInterface $db = null, $getShared = true)
|
||||
* @method static Negotiate negotiator(RequestInterface $request = null, $getShared = true)
|
||||
* @method static Pager pager(ConfigPager $config = null, RendererInterface $view = null, $getShared = true)
|
||||
* @method static Parser parser($viewPath = null, ConfigView $config = null, $getShared = true)
|
||||
* @method static RedirectResponse redirectresponse(App $config = null, $getShared = true)
|
||||
* @method static View renderer($viewPath = null, ConfigView $config = null, $getShared = true)
|
||||
* @method static IncomingRequest|CLIRequest request(App $config = null, $getShared = true)
|
||||
* @method static ResponseInterface response(App $config = null, $getShared = true)
|
||||
* @method static ResponseCache responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
|
||||
* @method static Router router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true)
|
||||
* @method static RouteCollection routes($getShared = true)
|
||||
* @method static Security security(App $config = null, $getShared = true)
|
||||
* @method static Session session(ConfigSession $config = null, $getShared = true)
|
||||
* @method static SiteURIFactory siteurifactory(App $config = null, Superglobals $superglobals = null, $getShared = true)
|
||||
* @method static Superglobals superglobals(array $server = null, array $get = null, bool $getShared = true)
|
||||
* @method static Throttler throttler($getShared = true)
|
||||
* @method static Timer timer($getShared = true)
|
||||
* @method static Toolbar toolbar(ConfigToolbar $config = null, $getShared = true)
|
||||
* @method static Typography typography($getShared = true)
|
||||
* @method static URI uri($uri = null, $getShared = true)
|
||||
* @method static ValidationInterface validation(ConfigValidation $config = null, $getShared = true)
|
||||
* @method static Cell viewcell($getShared = true)
|
||||
*/
|
||||
class BaseService
|
||||
{
|
||||
/**
|
||||
* Cache for instance of any services that
|
||||
* have been requested as a "shared" instance.
|
||||
* Keys should be lowercase service names.
|
||||
*
|
||||
* @var array<string, object> [key => instance]
|
||||
*/
|
||||
protected static $instances = [];
|
||||
|
||||
/**
|
||||
* Factory method list.
|
||||
*
|
||||
* @var array<string, (callable(mixed ...$params): object)> [key => callable]
|
||||
*/
|
||||
protected static array $factories = [];
|
||||
|
||||
/**
|
||||
* Mock objects for testing which are returned if exist.
|
||||
*
|
||||
* @var array<string, object> [key => instance]
|
||||
*/
|
||||
protected static $mocks = [];
|
||||
|
||||
/**
|
||||
* Have we already discovered other Services?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $discovered = false;
|
||||
|
||||
/**
|
||||
* A cache of other service classes we've found.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @deprecated 4.5.0 No longer used.
|
||||
*/
|
||||
protected static $services = [];
|
||||
|
||||
/**
|
||||
* A cache of the names of services classes found.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private static array $serviceNames = [];
|
||||
|
||||
/**
|
||||
* Simple method to get an entry fast.
|
||||
*
|
||||
* @param string $key Identifier of the entry to look for.
|
||||
*
|
||||
* @return object|null Entry.
|
||||
*/
|
||||
public static function get(string $key): ?object
|
||||
{
|
||||
return static::$instances[$key] ?? static::__callStatic($key, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an entry.
|
||||
*
|
||||
* @param string $key Identifier of the entry.
|
||||
*/
|
||||
public static function set(string $key, object $value): void
|
||||
{
|
||||
if (isset(static::$instances[$key])) {
|
||||
throw new InvalidArgumentException('The entry for "' . $key . '" is already set.');
|
||||
}
|
||||
|
||||
static::$instances[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides an existing entry.
|
||||
*
|
||||
* @param string $key Identifier of the entry.
|
||||
*/
|
||||
public static function override(string $key, object $value): void
|
||||
{
|
||||
static::$instances[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shared instance of any of the class' services.
|
||||
*
|
||||
* $key must be a name matching a service.
|
||||
*
|
||||
* @param array|bool|float|int|object|string|null ...$params
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
protected static function getSharedInstance(string $key, ...$params)
|
||||
{
|
||||
$key = strtolower($key);
|
||||
|
||||
// Returns mock if exists
|
||||
if (isset(static::$mocks[$key])) {
|
||||
return static::$mocks[$key];
|
||||
}
|
||||
|
||||
if (! isset(static::$instances[$key])) {
|
||||
// Make sure $getShared is false
|
||||
$params[] = false;
|
||||
|
||||
static::$instances[$key] = AppServices::$key(...$params);
|
||||
}
|
||||
|
||||
return static::$instances[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* The Autoloader class is the central class that handles our
|
||||
* spl_autoload_register method, and helper methods.
|
||||
*
|
||||
* @return Autoloader
|
||||
*/
|
||||
public static function autoloader(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
if (empty(static::$instances['autoloader'])) {
|
||||
static::$instances['autoloader'] = new Autoloader();
|
||||
}
|
||||
|
||||
return static::$instances['autoloader'];
|
||||
}
|
||||
|
||||
return new Autoloader();
|
||||
}
|
||||
|
||||
/**
|
||||
* The file locator provides utility methods for looking for non-classes
|
||||
* within namespaced folders, as well as convenience methods for
|
||||
* loading 'helpers', and 'libraries'.
|
||||
*
|
||||
* @return FileLocatorInterface
|
||||
*/
|
||||
public static function locator(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
if (empty(static::$instances['locator'])) {
|
||||
$cacheEnabled = class_exists(Optimize::class)
|
||||
&& (new Optimize())->locatorCacheEnabled;
|
||||
|
||||
if ($cacheEnabled) {
|
||||
static::$instances['locator'] = new FileLocatorCached(new FileLocator(static::autoloader()));
|
||||
} else {
|
||||
static::$instances['locator'] = new FileLocator(static::autoloader());
|
||||
}
|
||||
}
|
||||
|
||||
return static::$mocks['locator'] ?? static::$instances['locator'];
|
||||
}
|
||||
|
||||
return new FileLocator(static::autoloader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to perform case-insensitive calling of service
|
||||
* names.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
if (isset(static::$factories[$name])) {
|
||||
return static::$factories[$name](...$arguments);
|
||||
}
|
||||
|
||||
$service = static::serviceExists($name);
|
||||
|
||||
if ($service === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $service::$name(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the requested service is defined and return the declaring
|
||||
* class. Return null if not found.
|
||||
*/
|
||||
public static function serviceExists(string $name): ?string
|
||||
{
|
||||
static::buildServicesCache();
|
||||
|
||||
$services = array_merge(self::$serviceNames, [Services::class]);
|
||||
$name = strtolower($name);
|
||||
|
||||
foreach ($services as $service) {
|
||||
if (method_exists($service, $name)) {
|
||||
static::$factories[$name] = [$service, $name];
|
||||
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset shared instances and mocks for testing.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @testTag only available to test code
|
||||
*/
|
||||
public static function reset(bool $initAutoloader = true)
|
||||
{
|
||||
static::$mocks = [];
|
||||
static::$instances = [];
|
||||
static::$factories = [];
|
||||
|
||||
if ($initAutoloader) {
|
||||
static::autoloader()->initialize(new Autoload(), new Modules());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets any mock and shared instances for a single service.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @testTag only available to test code
|
||||
*/
|
||||
public static function resetSingle(string $name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
unset(static::$mocks[$name], static::$instances[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject mock object for testing.
|
||||
*
|
||||
* @param object $mock
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @testTag only available to test code
|
||||
*/
|
||||
public static function injectMock(string $name, $mock)
|
||||
{
|
||||
static::$instances[$name] = $mock;
|
||||
static::$mocks[strtolower($name)] = $mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the service cache.
|
||||
*/
|
||||
public static function resetServicesCache(): void
|
||||
{
|
||||
self::$serviceNames = [];
|
||||
static::$discovered = false;
|
||||
}
|
||||
|
||||
protected static function buildServicesCache(): void
|
||||
{
|
||||
if (! static::$discovered) {
|
||||
if ((new Modules())->shouldDiscover('services')) {
|
||||
$locator = static::locator();
|
||||
$files = $locator->search('Config/Services');
|
||||
|
||||
$systemPath = static::autoloader()->getNamespace('CodeIgniter')[0];
|
||||
|
||||
// Get instances of all service classes and cache them locally.
|
||||
foreach ($files as $file) {
|
||||
// Does not search `CodeIgniter` namespace to prevent from loading twice.
|
||||
if (str_starts_with($file, $systemPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classname = $locator->findQualifiedNameFromPath($file);
|
||||
|
||||
if ($classname === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($classname !== Services::class) {
|
||||
self::$serviceNames[] = $classname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static::$discovered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Environment-specific configuration
|
||||
*
|
||||
* @see \CodeIgniter\Config\DotEnvTest
|
||||
*/
|
||||
class DotEnv
|
||||
{
|
||||
/**
|
||||
* The directory where the .env file can be located.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Builds the path to our file.
|
||||
*/
|
||||
public function __construct(string $path, string $file = '.env')
|
||||
{
|
||||
$this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main entry point, will load the .env file and process it
|
||||
* so that we end up with all settings in the PHP environment vars
|
||||
* (i.e. getenv(), $_ENV, and $_SERVER)
|
||||
*/
|
||||
public function load(): bool
|
||||
{
|
||||
$vars = $this->parse();
|
||||
|
||||
return $vars !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the .env file into an array of key => value
|
||||
*/
|
||||
public function parse(): ?array
|
||||
{
|
||||
// We don't want to enforce the presence of a .env file, they should be optional.
|
||||
if (! is_file($this->path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure the file is readable
|
||||
if (! is_readable($this->path)) {
|
||||
throw new InvalidArgumentException("The .env file is not readable: {$this->path}");
|
||||
}
|
||||
|
||||
$vars = [];
|
||||
|
||||
$lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Is it a comment?
|
||||
if (str_starts_with(trim($line), '#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is an equal sign, then we know we are assigning a variable.
|
||||
if (str_contains($line, '=')) {
|
||||
[$name, $value] = $this->normaliseVariable($line);
|
||||
$vars[$name] = $value;
|
||||
$this->setVariable($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the variable into the environment. Will parse the string
|
||||
* first to look for {name}={value} pattern, ensure that nested
|
||||
* variables are handled, and strip it of single and double quotes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setVariable(string $name, string $value = '')
|
||||
{
|
||||
if (getenv($name, true) === false) {
|
||||
putenv("{$name}={$value}");
|
||||
}
|
||||
|
||||
if (empty($_ENV[$name])) {
|
||||
$_ENV[$name] = $value;
|
||||
}
|
||||
|
||||
if (empty($_SERVER[$name])) {
|
||||
$_SERVER[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses for assignment, cleans the $name and $value, and ensures
|
||||
* that nested variables are handled.
|
||||
*/
|
||||
public function normaliseVariable(string $name, string $value = ''): array
|
||||
{
|
||||
// Split our compound string into its parts.
|
||||
if (str_contains($name, '=')) {
|
||||
[$name, $value] = explode('=', $name, 2);
|
||||
}
|
||||
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
|
||||
// Sanitize the name
|
||||
$name = preg_replace('/^export[ \t]++(\S+)/', '$1', $name);
|
||||
$name = str_replace(['\'', '"'], '', $name);
|
||||
|
||||
// Sanitize the value
|
||||
$value = $this->sanitizeValue($value);
|
||||
$value = $this->resolveNestedVariables($value);
|
||||
|
||||
return [$name, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips quotes from the environment variable value.
|
||||
*
|
||||
* This was borrowed from the excellent phpdotenv with very few changes.
|
||||
* https://github.com/vlucas/phpdotenv
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function sanitizeValue(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Does it begin with a quote?
|
||||
if (strpbrk($value[0], '"\'') !== false) {
|
||||
// value starts with a quote
|
||||
$quote = $value[0];
|
||||
|
||||
$regexPattern = sprintf(
|
||||
'/^
|
||||
%1$s # match a quote at the start of the value
|
||||
( # capturing sub-pattern used
|
||||
(?: # we do not need to capture this
|
||||
[^%1$s\\\\] # any character other than a quote or backslash
|
||||
|\\\\\\\\ # or two backslashes together
|
||||
|\\\\%1$s # or an escaped quote e.g \"
|
||||
)* # as many characters that match the previous rules
|
||||
) # end of the capturing sub-pattern
|
||||
%1$s # and the closing quote
|
||||
.*$ # and discard any string after the closing quote
|
||||
/mx',
|
||||
$quote,
|
||||
);
|
||||
|
||||
$value = preg_replace($regexPattern, '$1', $value);
|
||||
$value = str_replace("\\{$quote}", $quote, $value);
|
||||
$value = str_replace('\\\\', '\\', $value);
|
||||
} else {
|
||||
$parts = explode(' #', $value, 2);
|
||||
$value = trim($parts[0]);
|
||||
|
||||
// Unquoted values cannot contain whitespace
|
||||
if (preg_match('/\s+/', $value) > 0) {
|
||||
throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.');
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the nested variables.
|
||||
*
|
||||
* Look for ${varname} patterns in the variable value and replace with an existing
|
||||
* environment variable.
|
||||
*
|
||||
* This was borrowed from the excellent phpdotenv with very few changes.
|
||||
* https://github.com/vlucas/phpdotenv
|
||||
*/
|
||||
protected function resolveNestedVariables(string $value): string
|
||||
{
|
||||
if (str_contains($value, '$')) {
|
||||
$value = preg_replace_callback(
|
||||
'/\${([a-zA-Z0-9_\.]+)}/',
|
||||
function ($matchedPatterns) {
|
||||
$nestedVariable = $this->getVariable($matchedPatterns[1]);
|
||||
|
||||
if ($nestedVariable === null) {
|
||||
return $matchedPatterns[0];
|
||||
}
|
||||
|
||||
return $nestedVariable;
|
||||
},
|
||||
$value,
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the different places for environment variables and return first value found.
|
||||
*
|
||||
* This was borrowed from the excellent phpdotenv with very few changes.
|
||||
* https://github.com/vlucas/phpdotenv
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getVariable(string $name)
|
||||
{
|
||||
switch (true) {
|
||||
case array_key_exists($name, $_ENV):
|
||||
return $_ENV[$name];
|
||||
|
||||
case array_key_exists($name, $_SERVER):
|
||||
return $_SERVER[$name];
|
||||
|
||||
default:
|
||||
$value = getenv($name);
|
||||
|
||||
// switch getenv default to null
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,564 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Factories for creating instances.
|
||||
*
|
||||
* Factories allow dynamic loading of components by their path
|
||||
* and name. The "shared instance" implementation provides a
|
||||
* large performance boost and helps keep code clean of lengthy
|
||||
* instantiation checks.
|
||||
*
|
||||
* @method static BaseConfig|null config(...$arguments)
|
||||
* @method static Model|null models(string $alias, array $options = [], ?ConnectionInterface &$conn = null)
|
||||
* @see \CodeIgniter\Config\FactoriesTest
|
||||
*/
|
||||
final class Factories
|
||||
{
|
||||
/**
|
||||
* Store of component-specific options, usually
|
||||
* from CodeIgniter\Config\Factory.
|
||||
*
|
||||
* @var array<string, array<string, bool|string|null>>
|
||||
*/
|
||||
private static array $options = [];
|
||||
|
||||
/**
|
||||
* Explicit options for the Config
|
||||
* component to prevent logic loops.
|
||||
*
|
||||
* @var array<string, bool|string|null>
|
||||
*/
|
||||
private static array $configOptions = [
|
||||
'component' => 'config',
|
||||
'path' => 'Config',
|
||||
'instanceOf' => null,
|
||||
'getShared' => true,
|
||||
'preferApp' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Mapping of class aliases to their true Fully Qualified Class Name (FQCN).
|
||||
*
|
||||
* Class aliases can be:
|
||||
* - FQCN. E.g., 'App\Lib\SomeLib'
|
||||
* - short classname. E.g., 'SomeLib'
|
||||
* - short classname with sub-directories. E.g., 'Sub/SomeLib'
|
||||
*
|
||||
* [component => [alias => FQCN]]
|
||||
*
|
||||
* @var array<string, array<string, class-string>>
|
||||
*/
|
||||
private static array $aliases = [];
|
||||
|
||||
/**
|
||||
* Store for instances of any component that
|
||||
* has been requested as "shared".
|
||||
*
|
||||
* A multi-dimensional array with components as
|
||||
* keys to the array of name-indexed instances.
|
||||
*
|
||||
* [component => [FQCN => instance]]
|
||||
*
|
||||
* @var array<string, array<class-string, object>>
|
||||
*/
|
||||
private static array $instances = [];
|
||||
|
||||
/**
|
||||
* Whether the component instances are updated?
|
||||
*
|
||||
* @var array<string, true> [component => true]
|
||||
*
|
||||
* @internal For caching only
|
||||
*/
|
||||
private static array $updated = [];
|
||||
|
||||
/**
|
||||
* Define the class to load. You can *override* the concrete class.
|
||||
*
|
||||
* @param string $component Lowercase, plural component name
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
* @param class-string $classname FQCN to be loaded
|
||||
*/
|
||||
public static function define(string $component, string $alias, string $classname): void
|
||||
{
|
||||
$component = strtolower($component);
|
||||
|
||||
if (isset(self::$aliases[$component][$alias])) {
|
||||
if (self::$aliases[$component][$alias] === $classname) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
'Already defined in Factories: ' . $component . ' ' . $alias . ' -> ' . self::$aliases[$component][$alias],
|
||||
);
|
||||
}
|
||||
|
||||
if (! class_exists($classname)) {
|
||||
throw new InvalidArgumentException('No such class: ' . $classname);
|
||||
}
|
||||
|
||||
// Force a configuration to exist for this component.
|
||||
// Otherwise, getOptions() will reset the component.
|
||||
self::getOptions($component);
|
||||
|
||||
self::$aliases[$component][$alias] = $classname;
|
||||
self::$updated[$component] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads instances based on the method component name. Either
|
||||
* creates a new instance or returns an existing shared instance.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public static function __callStatic(string $component, array $arguments)
|
||||
{
|
||||
$component = strtolower($component);
|
||||
|
||||
// First argument is the class alias, second is options
|
||||
$alias = trim(array_shift($arguments), '\\ ');
|
||||
$options = array_shift($arguments) ?? [];
|
||||
|
||||
// Determine the component-specific options
|
||||
$options = array_merge(self::getOptions($component), $options);
|
||||
|
||||
if (! $options['getShared']) {
|
||||
if (isset(self::$aliases[$options['component']][$alias])) {
|
||||
$class = self::$aliases[$options['component']][$alias];
|
||||
|
||||
return new $class(...$arguments);
|
||||
}
|
||||
|
||||
// Try to locate the class
|
||||
$class = self::locateClass($options, $alias);
|
||||
if ($class !== null) {
|
||||
return new $class(...$arguments);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for an existing definition
|
||||
$instance = self::getDefinedInstance($options, $alias, $arguments);
|
||||
if ($instance !== null) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Try to locate the class
|
||||
if (($class = self::locateClass($options, $alias)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
self::createInstance($options['component'], $class, $arguments);
|
||||
self::setAlias($options['component'], $alias, $class);
|
||||
|
||||
return self::$instances[$options['component']][$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple method to get the shared instance fast.
|
||||
*/
|
||||
public static function get(string $component, string $alias): ?object
|
||||
{
|
||||
if (isset(self::$aliases[$component][$alias])) {
|
||||
$class = self::$aliases[$component][$alias];
|
||||
|
||||
if (isset(self::$instances[$component][$class])) {
|
||||
return self::$instances[$component][$class];
|
||||
}
|
||||
}
|
||||
|
||||
return self::__callStatic($component, [$alias]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the defined instance. If not exists, creates new one.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
private static function getDefinedInstance(array $options, string $alias, array $arguments)
|
||||
{
|
||||
// The alias is already defined.
|
||||
if (isset(self::$aliases[$options['component']][$alias])) {
|
||||
$class = self::$aliases[$options['component']][$alias];
|
||||
|
||||
// Need to verify if the shared instance matches the request
|
||||
if (self::verifyInstanceOf($options, $class)) {
|
||||
// Check for an existing instance
|
||||
if (isset(self::$instances[$options['component']][$class])) {
|
||||
return self::$instances[$options['component']][$class];
|
||||
}
|
||||
|
||||
self::createInstance($options['component'], $class, $arguments);
|
||||
|
||||
return self::$instances[$options['component']][$class];
|
||||
}
|
||||
}
|
||||
|
||||
// Try to locate the class
|
||||
if (($class = self::locateClass($options, $alias)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for an existing instance for the class
|
||||
if (isset(self::$instances[$options['component']][$class])) {
|
||||
self::setAlias($options['component'], $alias, $class);
|
||||
|
||||
return self::$instances[$options['component']][$class];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the shared instance.
|
||||
*/
|
||||
private static function createInstance(string $component, string $class, array $arguments): void
|
||||
{
|
||||
self::$instances[$component][$class] = new $class(...$arguments);
|
||||
self::$updated[$component] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets alias
|
||||
*/
|
||||
private static function setAlias(string $component, string $alias, string $class): void
|
||||
{
|
||||
self::$aliases[$component][$alias] = $class;
|
||||
self::$updated[$component] = true;
|
||||
|
||||
// If a short classname is specified, also register FQCN to share the instance.
|
||||
if (! isset(self::$aliases[$component][$class]) && ! self::isNamespaced($alias)) {
|
||||
self::$aliases[$component][$class] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the component Config?
|
||||
*
|
||||
* @param string $component Lowercase, plural component name
|
||||
*/
|
||||
private static function isConfig(string $component): bool
|
||||
{
|
||||
return $component === 'config';
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a component class
|
||||
*
|
||||
* @param array $options The array of component-specific directives
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
*/
|
||||
private static function locateClass(array $options, string $alias): ?string
|
||||
{
|
||||
// Check for low-hanging fruit
|
||||
if (
|
||||
class_exists($alias, false)
|
||||
&& self::verifyPreferApp($options, $alias)
|
||||
&& self::verifyInstanceOf($options, $alias)
|
||||
) {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
// Determine the relative class names we need
|
||||
$basename = self::getBasename($alias);
|
||||
$appname = self::isConfig($options['component'])
|
||||
? 'Config\\' . $basename
|
||||
: rtrim(APP_NAMESPACE, '\\') . '\\' . $options['path'] . '\\' . $basename;
|
||||
|
||||
// If an App version was requested then see if it verifies
|
||||
if (
|
||||
// preferApp is used only for no namespaced class.
|
||||
! self::isNamespaced($alias)
|
||||
&& $options['preferApp'] && class_exists($appname)
|
||||
&& self::verifyInstanceOf($options, $alias)
|
||||
) {
|
||||
return $appname;
|
||||
}
|
||||
|
||||
// If we have ruled out an App version and the class exists then try it
|
||||
if (class_exists($alias) && self::verifyInstanceOf($options, $alias)) {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
// Have to do this the hard way...
|
||||
/** @var FileLocatorInterface */
|
||||
$locator = service('locator');
|
||||
|
||||
// Check if the class alias was namespaced
|
||||
if (self::isNamespaced($alias)) {
|
||||
if (! $file = $locator->locateFile($alias, $options['path'])) {
|
||||
return null;
|
||||
}
|
||||
$files = [$file];
|
||||
}
|
||||
// No namespace? Search for it
|
||||
// Check all namespaces, prioritizing App and modules
|
||||
elseif (($files = $locator->search($options['path'] . DIRECTORY_SEPARATOR . $alias)) === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check all files for a valid class
|
||||
foreach ($files as $file) {
|
||||
$class = $locator->findQualifiedNameFromPath($file);
|
||||
|
||||
if ($class !== false && self::verifyInstanceOf($options, $class)) {
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the class alias namespaced or not?
|
||||
*
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
*/
|
||||
private static function isNamespaced(string $alias): bool
|
||||
{
|
||||
return str_contains($alias, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a class & config satisfy the "preferApp" option
|
||||
*
|
||||
* @param array $options The array of component-specific directives
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
*/
|
||||
private static function verifyPreferApp(array $options, string $alias): bool
|
||||
{
|
||||
// Anything without that restriction passes
|
||||
if (! $options['preferApp']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special case for Config since its App namespace is actually \Config
|
||||
if (self::isConfig($options['component'])) {
|
||||
return str_starts_with($alias, 'Config');
|
||||
}
|
||||
|
||||
return str_starts_with($alias, APP_NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a class & config satisfy the "instanceOf" option
|
||||
*
|
||||
* @param array $options The array of component-specific directives
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
*/
|
||||
private static function verifyInstanceOf(array $options, string $alias): bool
|
||||
{
|
||||
// Anything without that restriction passes
|
||||
if (! $options['instanceOf']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is_a($alias, $options['instanceOf'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component-specific configuration
|
||||
*
|
||||
* @param string $component Lowercase, plural component name
|
||||
*
|
||||
* @return array<string, bool|string|null>
|
||||
*
|
||||
* @internal For testing only
|
||||
* @testTag
|
||||
*/
|
||||
public static function getOptions(string $component): array
|
||||
{
|
||||
$component = strtolower($component);
|
||||
|
||||
// Check for a stored version
|
||||
if (isset(self::$options[$component])) {
|
||||
return self::$options[$component];
|
||||
}
|
||||
|
||||
$values = self::isConfig($component)
|
||||
// Handle Config as a special case to prevent logic loops
|
||||
? self::$configOptions
|
||||
// Load values from the best Factory configuration (will include Registrars)
|
||||
: config('Factory')->{$component} ?? [];
|
||||
|
||||
// The setOptions() reset the component. So getOptions() may reset
|
||||
// the component.
|
||||
return self::setOptions($component, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes, stores, and returns the configuration for a specific component
|
||||
*
|
||||
* @param string $component Lowercase, plural component name
|
||||
* @param array $values option values
|
||||
*
|
||||
* @return array<string, bool|string|null> The result after applying defaults and normalization
|
||||
*/
|
||||
public static function setOptions(string $component, array $values): array
|
||||
{
|
||||
$component = strtolower($component);
|
||||
|
||||
// Allow the config to replace the component name, to support "aliases"
|
||||
$values['component'] = strtolower($values['component'] ?? $component);
|
||||
|
||||
// Reset this component so instances can be rediscovered with the updated config
|
||||
self::reset($values['component']);
|
||||
|
||||
// If no path was available then use the component
|
||||
$values['path'] = trim($values['path'] ?? ucfirst($values['component']), '\\ ');
|
||||
|
||||
// Add defaults for any missing values
|
||||
$values = array_merge(Factory::$default, $values);
|
||||
|
||||
// Store the result to the supplied name and potential alias
|
||||
self::$options[$component] = $values;
|
||||
self::$options[$values['component']] = $values;
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the static arrays, optionally just for one component
|
||||
*
|
||||
* @param string|null $component Lowercase, plural component name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset(?string $component = null)
|
||||
{
|
||||
if ($component !== null) {
|
||||
unset(
|
||||
self::$options[$component],
|
||||
self::$aliases[$component],
|
||||
self::$instances[$component],
|
||||
self::$updated[$component],
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self::$options = [];
|
||||
self::$aliases = [];
|
||||
self::$instances = [];
|
||||
self::$updated = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for injecting mock instances
|
||||
*
|
||||
* @param string $component Lowercase, plural component name
|
||||
* @param string $alias Class alias. See the $aliases property.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @internal For testing only
|
||||
* @testTag
|
||||
*/
|
||||
public static function injectMock(string $component, string $alias, object $instance)
|
||||
{
|
||||
$component = strtolower($component);
|
||||
|
||||
// Force a configuration to exist for this component
|
||||
self::getOptions($component);
|
||||
|
||||
$class = $instance::class;
|
||||
|
||||
self::$instances[$component][$class] = $instance;
|
||||
self::$aliases[$component][$alias] = $class;
|
||||
|
||||
if (self::isConfig($component)) {
|
||||
if (self::isNamespaced($alias)) {
|
||||
self::$aliases[$component][self::getBasename($alias)] = $class;
|
||||
} else {
|
||||
self::$aliases[$component]['Config\\' . $alias] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a basename from a class alias, namespaced or not.
|
||||
*
|
||||
* @internal For testing only
|
||||
* @testTag
|
||||
*/
|
||||
public static function getBasename(string $alias): string
|
||||
{
|
||||
// Determine the basename
|
||||
if ($basename = strrchr($alias, '\\')) {
|
||||
return substr($basename, 1);
|
||||
}
|
||||
|
||||
return $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets component data for caching.
|
||||
*
|
||||
* @return array{
|
||||
* options: array<string, bool|string|null>,
|
||||
* aliases: array<string, class-string>,
|
||||
* instances: array<class-string, object>,
|
||||
* }
|
||||
*
|
||||
* @internal For caching only
|
||||
*/
|
||||
public static function getComponentInstances(string $component): array
|
||||
{
|
||||
if (! isset(self::$aliases[$component])) {
|
||||
return [
|
||||
'options' => [],
|
||||
'aliases' => [],
|
||||
'instances' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'options' => self::$options[$component],
|
||||
'aliases' => self::$aliases[$component],
|
||||
'instances' => self::$instances[$component],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets component data
|
||||
*
|
||||
* @internal For caching only
|
||||
*/
|
||||
public static function setComponentInstances(string $component, array $data): void
|
||||
{
|
||||
self::$options[$component] = $data['options'];
|
||||
self::$aliases[$component] = $data['aliases'];
|
||||
self::$instances[$component] = $data['instances'];
|
||||
|
||||
unset(self::$updated[$component]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the component instances are updated?
|
||||
*
|
||||
* @internal For caching only
|
||||
*/
|
||||
public static function isUpdated(string $component): bool
|
||||
{
|
||||
return isset(self::$updated[$component]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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\Config;
|
||||
|
||||
/**
|
||||
* Factories Configuration file.
|
||||
*
|
||||
* Provides overriding directives for how
|
||||
* Factories should handle discovery and
|
||||
* instantiation of specific components.
|
||||
* Each property should correspond to the
|
||||
* lowercase, plural component name.
|
||||
*/
|
||||
class Factory extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Supplies a default set of options to merge for
|
||||
* all unspecified factory components.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $default = [
|
||||
'component' => null,
|
||||
'path' => null,
|
||||
'instanceOf' => null,
|
||||
'getShared' => true,
|
||||
'preferApp' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Specifies that Models should always favor child
|
||||
* classes to allow easy extension of module Models.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $models = [
|
||||
'preferApp' => true,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Filters\Cors;
|
||||
use CodeIgniter\Filters\CSRF;
|
||||
use CodeIgniter\Filters\DebugToolbar;
|
||||
use CodeIgniter\Filters\ForceHTTPS;
|
||||
use CodeIgniter\Filters\Honeypot;
|
||||
use CodeIgniter\Filters\InvalidChars;
|
||||
use CodeIgniter\Filters\PageCache;
|
||||
use CodeIgniter\Filters\PerformanceMetrics;
|
||||
use CodeIgniter\Filters\SecureHeaders;
|
||||
|
||||
/**
|
||||
* Filters configuration
|
||||
*/
|
||||
class Filters extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Configures aliases for Filter classes to
|
||||
* make reading things nicer and simpler.
|
||||
*
|
||||
* @var array<string, class-string|list<class-string>>
|
||||
*
|
||||
* [filter_name => classname]
|
||||
* or [filter_name => [classname1, classname2, ...]]
|
||||
*/
|
||||
public array $aliases = [
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'secureheaders' => SecureHeaders::class,
|
||||
'cors' => Cors::class,
|
||||
'forcehttps' => ForceHTTPS::class,
|
||||
'pagecache' => PageCache::class,
|
||||
'performance' => PerformanceMetrics::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of special required filters.
|
||||
*
|
||||
* The filters listed here are special. They are applied before and after
|
||||
* other kinds of filters, and always applied even if a route does not exist.
|
||||
*
|
||||
* Filters set by default provide framework functionality. If removed,
|
||||
* those functions will no longer work.
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
|
||||
*
|
||||
* @var array{before: list<string>, after: list<string>}
|
||||
*/
|
||||
public array $required = [
|
||||
'before' => [
|
||||
'forcehttps', // Force Global Secure Requests
|
||||
'pagecache', // Web Page Caching
|
||||
],
|
||||
'after' => [
|
||||
'pagecache', // Web Page Caching
|
||||
'performance', // Performance Metrics
|
||||
'toolbar', // Debug Toolbar
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* List of filter aliases that are always
|
||||
* applied before and after every request.
|
||||
*
|
||||
* @var array<string, array<string, array<string, string>>>|array<string, list<string>>
|
||||
*/
|
||||
public array $globals = [
|
||||
'before' => [
|
||||
// 'honeypot',
|
||||
// 'csrf',
|
||||
// 'invalidchars',
|
||||
],
|
||||
'after' => [
|
||||
// 'honeypot',
|
||||
// 'secureheaders',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* List of filter aliases that works on a
|
||||
* particular HTTP method (GET, POST, etc.).
|
||||
*
|
||||
* Example:
|
||||
* 'POST' => ['foo', 'bar']
|
||||
*
|
||||
* If you use this, you should disable auto-routing because auto-routing
|
||||
* permits any HTTP method to access a controller. Accessing the controller
|
||||
* with a method you don't expect could bypass the filter.
|
||||
*
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
public array $methods = [];
|
||||
|
||||
/**
|
||||
* List of filter aliases that should run on any
|
||||
* before or after URI patterns.
|
||||
*
|
||||
* Example:
|
||||
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
public array $filters = [];
|
||||
}
|
||||
@@ -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\Config;
|
||||
|
||||
/**
|
||||
* Describes foreign characters for transliteration with the text helper.
|
||||
*/
|
||||
class ForeignCharacters
|
||||
{
|
||||
/**
|
||||
* The list of foreign characters.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $characterList = [
|
||||
'/ä|æ|ǽ/' => 'ae',
|
||||
'/ö|œ/' => 'oe',
|
||||
'/ü/' => 'ue',
|
||||
'/Ä/' => 'Ae',
|
||||
'/Ü/' => 'Ue',
|
||||
'/Ö/' => 'Oe',
|
||||
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A',
|
||||
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a',
|
||||
'/Б/' => 'B',
|
||||
'/б/' => 'b',
|
||||
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
|
||||
'/ç|ć|ĉ|ċ|č/' => 'c',
|
||||
'/Д/' => 'D',
|
||||
'/д/' => 'd',
|
||||
'/Ð|Ď|Đ|Δ/' => 'Dj',
|
||||
'/ð|ď|đ|δ/' => 'dj',
|
||||
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E',
|
||||
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e',
|
||||
'/Ф/' => 'F',
|
||||
'/ф/' => 'f',
|
||||
'/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G',
|
||||
'/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g',
|
||||
'/Ĥ|Ħ/' => 'H',
|
||||
'/ĥ|ħ/' => 'h',
|
||||
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I',
|
||||
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i',
|
||||
'/Ĵ/' => 'J',
|
||||
'/ĵ/' => 'j',
|
||||
'/Ķ|Κ|К/' => 'K',
|
||||
'/ķ|κ|к/' => 'k',
|
||||
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L',
|
||||
'/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l',
|
||||
'/М/' => 'M',
|
||||
'/м/' => 'm',
|
||||
'/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N',
|
||||
'/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n',
|
||||
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O',
|
||||
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o',
|
||||
'/П/' => 'P',
|
||||
'/п/' => 'p',
|
||||
'/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R',
|
||||
'/ŕ|ŗ|ř|ρ|р/' => 'r',
|
||||
'/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S',
|
||||
'/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's',
|
||||
'/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T',
|
||||
'/ț|ţ|ť|ŧ|т/' => 't',
|
||||
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U',
|
||||
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u',
|
||||
'/Ƴ|Ɏ|Ỵ|Ẏ|Ӳ|Ӯ|Ў|Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y',
|
||||
'/ẙ|ʏ|ƴ|ɏ|ỵ|ẏ|ӳ|ӯ|ў|ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y',
|
||||
'/В/' => 'V',
|
||||
'/в/' => 'v',
|
||||
'/Ŵ/' => 'W',
|
||||
'/ŵ/' => 'w',
|
||||
'/Ź|Ż|Ž|Ζ|З/' => 'Z',
|
||||
'/ź|ż|ž|ζ|з/' => 'z',
|
||||
'/Æ|Ǽ/' => 'AE',
|
||||
'/ß/' => 'ss',
|
||||
'/IJ/' => 'IJ',
|
||||
'/ij/' => 'ij',
|
||||
'/Œ/' => 'OE',
|
||||
'/ƒ/' => 'f',
|
||||
'/ξ/' => 'ks',
|
||||
'/π/' => 'p',
|
||||
'/β/' => 'v',
|
||||
'/μ/' => 'm',
|
||||
'/ψ/' => 'ps',
|
||||
'/Ё/' => 'Yo',
|
||||
'/ё/' => 'yo',
|
||||
'/Є/' => 'Ye',
|
||||
'/є/' => 'ye',
|
||||
'/Ї/' => 'Yi',
|
||||
'/Ж/' => 'Zh',
|
||||
'/ж/' => 'zh',
|
||||
'/Х/' => 'Kh',
|
||||
'/х/' => 'kh',
|
||||
'/Ц/' => 'Ts',
|
||||
'/ц/' => 'ts',
|
||||
'/Ч/' => 'Ch',
|
||||
'/ч/' => 'ch',
|
||||
'/Ш/' => 'Sh',
|
||||
'/ш/' => 'sh',
|
||||
'/Щ/' => 'Shch',
|
||||
'/щ/' => 'shch',
|
||||
'/Ъ|ъ|Ь|ь/' => '',
|
||||
'/Ю/' => 'Yu',
|
||||
'/ю/' => 'yu',
|
||||
'/Я/' => 'Ya',
|
||||
'/я/' => 'ya',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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\Config;
|
||||
|
||||
/**
|
||||
* Publisher Configuration
|
||||
*
|
||||
* Defines basic security restrictions for the Publisher class
|
||||
* to prevent abuse by injecting malicious files into a project.
|
||||
*/
|
||||
class Publisher extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* A list of allowed destinations with a (pseudo-)regex
|
||||
* of allowed files for each destination.
|
||||
* Attempts to publish to directories not in this list will
|
||||
* result in a PublisherException. Files that do no fit the
|
||||
* pattern will cause copy/merge to fail.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $restrictions = [
|
||||
ROOTPATH => '*',
|
||||
FCPATH => '#\.(?css|js|map|htm?|xml|json|webmanifest|tff|eot|woff?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i',
|
||||
];
|
||||
|
||||
/**
|
||||
* Disables Registrars to prevent modules from altering the restrictions.
|
||||
*/
|
||||
final protected function registerProperties(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?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\Config;
|
||||
|
||||
/**
|
||||
* Routing configuration
|
||||
*/
|
||||
class Routing extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* An array of files that contain route definitions.
|
||||
* Route files are read in order, with the first match
|
||||
* found taking precedence.
|
||||
*
|
||||
* Default: APPPATH . 'Config/Routes.php'
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $routeFiles = [
|
||||
APPPATH . 'Config/Routes.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* For Defined Routes and Auto Routing.
|
||||
* The default namespace to use for Controllers when no other
|
||||
* namespace has been specified.
|
||||
*
|
||||
* Default: 'App\Controllers'
|
||||
*/
|
||||
public string $defaultNamespace = 'App\Controllers';
|
||||
|
||||
/**
|
||||
* For Auto Routing.
|
||||
* The default controller to use when no other controller has been
|
||||
* specified.
|
||||
*
|
||||
* Default: 'Home'
|
||||
*/
|
||||
public string $defaultController = 'Home';
|
||||
|
||||
/**
|
||||
* For Defined Routes and Auto Routing.
|
||||
* The default method to call on the controller when no other
|
||||
* method has been set in the route.
|
||||
*
|
||||
* Default: 'index'
|
||||
*/
|
||||
public string $defaultMethod = 'index';
|
||||
|
||||
/**
|
||||
* For Auto Routing.
|
||||
* Whether to translate dashes in URIs for controller/method to underscores.
|
||||
* Primarily useful when using the auto-routing.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $translateURIDashes = false;
|
||||
|
||||
/**
|
||||
* Sets the class/method that should be called if routing doesn't
|
||||
* find a match. It can be the controller/method name like: Users::index
|
||||
*
|
||||
* This setting is passed to the Router class and handled there.
|
||||
*
|
||||
* If you want to use a closure, you will have to set it in the
|
||||
* routes file by calling:
|
||||
*
|
||||
* $routes->set404Override(function() {
|
||||
* // Do something here
|
||||
* });
|
||||
*
|
||||
* Example:
|
||||
* public $override404 = 'App\Errors::show404';
|
||||
*/
|
||||
public ?string $override404 = null;
|
||||
|
||||
/**
|
||||
* If TRUE, the system will attempt to match the URI against
|
||||
* Controllers by matching each segment against folders/files
|
||||
* in APPPATH/Controllers, when a match wasn't found against
|
||||
* defined routes.
|
||||
*
|
||||
* If FALSE, will stop searching and do NO automatic routing.
|
||||
*/
|
||||
public bool $autoRoute = false;
|
||||
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* If TRUE, will enable the use of the 'prioritize' option
|
||||
* when defining routes.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $prioritize = false;
|
||||
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* If TRUE, matched multiple URI segments will be passed as one parameter.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $multipleSegmentsOneParam = false;
|
||||
|
||||
/**
|
||||
* For Auto Routing (Improved).
|
||||
* Map of URI segments and namespaces.
|
||||
*
|
||||
* The key is the first URI segment. The value is the controller namespace.
|
||||
* E.g.,
|
||||
* [
|
||||
* 'blog' => 'Acme\Blog\Controllers',
|
||||
* ]
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $moduleRoutes = [];
|
||||
|
||||
/**
|
||||
* For Auto Routing (Improved).
|
||||
* Whether to translate dashes in URIs for controller/method to CamelCase.
|
||||
* E.g., blog-controller -> BlogController
|
||||
*
|
||||
* If you enable this, $translateURIDashes is ignored.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $translateUriToCamelCase = false;
|
||||
}
|
||||
@@ -0,0 +1,866 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\Cache\CacheFactory;
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Cache\ResponseCache;
|
||||
use CodeIgniter\CLI\Commands;
|
||||
use CodeIgniter\CodeIgniter;
|
||||
use CodeIgniter\Database\ConnectionInterface;
|
||||
use CodeIgniter\Database\MigrationRunner;
|
||||
use CodeIgniter\Debug\Exceptions;
|
||||
use CodeIgniter\Debug\Iterator;
|
||||
use CodeIgniter\Debug\Timer;
|
||||
use CodeIgniter\Debug\Toolbar;
|
||||
use CodeIgniter\Email\Email;
|
||||
use CodeIgniter\Encryption\EncrypterInterface;
|
||||
use CodeIgniter\Encryption\Encryption;
|
||||
use CodeIgniter\Filters\Filters;
|
||||
use CodeIgniter\Format\Format;
|
||||
use CodeIgniter\Honeypot\Honeypot;
|
||||
use CodeIgniter\HTTP\CLIRequest;
|
||||
use CodeIgniter\HTTP\ContentSecurityPolicy;
|
||||
use CodeIgniter\HTTP\CURLRequest;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\Negotiate;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\HTTP\SiteURIFactory;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\HTTP\UserAgent;
|
||||
use CodeIgniter\Images\Handlers\BaseHandler;
|
||||
use CodeIgniter\Language\Language;
|
||||
use CodeIgniter\Log\Logger;
|
||||
use CodeIgniter\Pager\Pager;
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
use CodeIgniter\Router\RouteCollectionInterface;
|
||||
use CodeIgniter\Router\Router;
|
||||
use CodeIgniter\Security\Security;
|
||||
use CodeIgniter\Session\Handlers\BaseHandler as SessionBaseHandler;
|
||||
use CodeIgniter\Session\Handlers\Database\MySQLiHandler;
|
||||
use CodeIgniter\Session\Handlers\Database\PostgreHandler;
|
||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||
use CodeIgniter\Session\Session;
|
||||
use CodeIgniter\Superglobals;
|
||||
use CodeIgniter\Throttle\Throttler;
|
||||
use CodeIgniter\Typography\Typography;
|
||||
use CodeIgniter\Validation\Validation;
|
||||
use CodeIgniter\Validation\ValidationInterface;
|
||||
use CodeIgniter\View\Cell;
|
||||
use CodeIgniter\View\Parser;
|
||||
use CodeIgniter\View\RendererInterface;
|
||||
use CodeIgniter\View\View;
|
||||
use Config\App;
|
||||
use Config\Cache;
|
||||
use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig;
|
||||
use Config\ContentSecurityPolicy as CSPConfig;
|
||||
use Config\Database;
|
||||
use Config\Email as EmailConfig;
|
||||
use Config\Encryption as EncryptionConfig;
|
||||
use Config\Exceptions as ExceptionsConfig;
|
||||
use Config\Filters as FiltersConfig;
|
||||
use Config\Format as FormatConfig;
|
||||
use Config\Honeypot as HoneypotConfig;
|
||||
use Config\Images;
|
||||
use Config\Logger as LoggerConfig;
|
||||
use Config\Migrations;
|
||||
use Config\Modules;
|
||||
use Config\Pager as PagerConfig;
|
||||
use Config\Paths;
|
||||
use Config\Routing;
|
||||
use Config\Security as SecurityConfig;
|
||||
use Config\Services as AppServices;
|
||||
use Config\Session as SessionConfig;
|
||||
use Config\Toolbar as ToolbarConfig;
|
||||
use Config\Validation as ValidationConfig;
|
||||
use Config\View as ViewConfig;
|
||||
use InvalidArgumentException;
|
||||
use Locale;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
*
|
||||
* Services are simply other classes/libraries that the system uses
|
||||
* to do its job. This is used by CodeIgniter to allow the core of the
|
||||
* framework to be swapped out easily without affecting the usage within
|
||||
* the rest of your application.
|
||||
*
|
||||
* This is used in place of a Dependency Injection container primarily
|
||||
* due to its simplicity, which allows a better long-term maintenance
|
||||
* of the applications built on top of CodeIgniter. A bonus side-effect
|
||||
* is that IDEs are able to determine what class you are calling
|
||||
* whereas with DI Containers there usually isn't a way for them to do this.
|
||||
*
|
||||
* @see http://blog.ircmaxell.com/2015/11/simple-easy-risk-and-change.html
|
||||
* @see http://www.infoq.com/presentations/Simple-Made-Easy
|
||||
* @see \CodeIgniter\Config\ServicesTest
|
||||
*/
|
||||
class Services extends BaseService
|
||||
{
|
||||
/**
|
||||
* The cache class provides a simple way to store and retrieve
|
||||
* complex data for later.
|
||||
*
|
||||
* @return CacheInterface
|
||||
*/
|
||||
public static function cache(?Cache $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('cache', $config);
|
||||
}
|
||||
|
||||
$config ??= config(Cache::class);
|
||||
|
||||
return CacheFactory::getHandler($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The CLI Request class provides for ways to interact with
|
||||
* a command line request.
|
||||
*
|
||||
* @return CLIRequest
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function clirequest(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('clirequest', $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
|
||||
return new CLIRequest($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* CodeIgniter, the core of the framework.
|
||||
*
|
||||
* @return CodeIgniter
|
||||
*/
|
||||
public static function codeigniter(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('codeigniter', $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
|
||||
return new CodeIgniter($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The commands utility for running and working with CLI commands.
|
||||
*
|
||||
* @return Commands
|
||||
*/
|
||||
public static function commands(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('commands');
|
||||
}
|
||||
|
||||
return new Commands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Content Security Policy
|
||||
*
|
||||
* @return ContentSecurityPolicy
|
||||
*/
|
||||
public static function csp(?CSPConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('csp', $config);
|
||||
}
|
||||
|
||||
$config ??= config(ContentSecurityPolicyConfig::class);
|
||||
|
||||
return new ContentSecurityPolicy($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The CURL Request class acts as a simple HTTP client for interacting
|
||||
* with other servers, typically through APIs.
|
||||
*
|
||||
* @return CURLRequest
|
||||
*/
|
||||
public static function curlrequest(array $options = [], ?ResponseInterface $response = null, ?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('curlrequest', $options, $response, $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
$response ??= new Response($config);
|
||||
|
||||
return new CURLRequest(
|
||||
$config,
|
||||
new URI($options['baseURI'] ?? null),
|
||||
$response,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Email class allows you to send email via mail, sendmail, SMTP.
|
||||
*
|
||||
* @param array|EmailConfig|null $config
|
||||
*
|
||||
* @return Email
|
||||
*/
|
||||
public static function email($config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('email', $config);
|
||||
}
|
||||
|
||||
if (empty($config) || (! is_array($config) && ! $config instanceof EmailConfig)) {
|
||||
$config = config(EmailConfig::class);
|
||||
}
|
||||
|
||||
return new Email($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Encryption class provides two-way encryption.
|
||||
*
|
||||
* @param bool $getShared
|
||||
*
|
||||
* @return EncrypterInterface Encryption handler
|
||||
*/
|
||||
public static function encrypter(?EncryptionConfig $config = null, $getShared = false)
|
||||
{
|
||||
if ($getShared === true) {
|
||||
return static::getSharedInstance('encrypter', $config);
|
||||
}
|
||||
|
||||
$config ??= config(EncryptionConfig::class);
|
||||
$encryption = new Encryption($config);
|
||||
|
||||
return $encryption->initialize($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Exceptions class holds the methods that handle:
|
||||
*
|
||||
* - set_exception_handler
|
||||
* - set_error_handler
|
||||
* - register_shutdown_function
|
||||
*
|
||||
* @return Exceptions
|
||||
*/
|
||||
public static function exceptions(
|
||||
?ExceptionsConfig $config = null,
|
||||
bool $getShared = true,
|
||||
) {
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('exceptions', $config);
|
||||
}
|
||||
|
||||
$config ??= config(ExceptionsConfig::class);
|
||||
|
||||
return new Exceptions($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters allow you to run tasks before and/or after a controller
|
||||
* is executed. During before filters, the request can be modified,
|
||||
* and actions taken based on the request, while after filters can
|
||||
* act on or modify the response itself before it is sent to the client.
|
||||
*
|
||||
* @return Filters
|
||||
*/
|
||||
public static function filters(?FiltersConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('filters', $config);
|
||||
}
|
||||
|
||||
$config ??= config(FiltersConfig::class);
|
||||
|
||||
return new Filters($config, AppServices::get('request'), AppServices::get('response'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Format class is a convenient place to create Formatters.
|
||||
*
|
||||
* @return Format
|
||||
*/
|
||||
public static function format(?FormatConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('format', $config);
|
||||
}
|
||||
|
||||
$config ??= config(FormatConfig::class);
|
||||
|
||||
return new Format($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Honeypot provides a secret input on forms that bots should NOT
|
||||
* fill in, providing an additional safeguard when accepting user input.
|
||||
*
|
||||
* @return Honeypot
|
||||
*/
|
||||
public static function honeypot(?HoneypotConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('honeypot', $config);
|
||||
}
|
||||
|
||||
$config ??= config(HoneypotConfig::class);
|
||||
|
||||
return new Honeypot($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts as a factory for ImageHandler classes and returns an instance
|
||||
* of the handler. Used like service('image')->withFile($path)->rotate(90)->save();
|
||||
*
|
||||
* @return BaseHandler
|
||||
*/
|
||||
public static function image(?string $handler = null, ?Images $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('image', $handler, $config);
|
||||
}
|
||||
|
||||
$config ??= config(Images::class);
|
||||
assert($config instanceof Images);
|
||||
|
||||
$handler = $handler !== null && $handler !== '' && $handler !== '0' ? $handler : $config->defaultHandler;
|
||||
$class = $config->handlers[$handler];
|
||||
|
||||
return new $class($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Iterator class provides a simple way of looping over a function
|
||||
* and timing the results and memory usage. Used when debugging and
|
||||
* optimizing applications.
|
||||
*
|
||||
* @return Iterator
|
||||
*/
|
||||
public static function iterator(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('iterator');
|
||||
}
|
||||
|
||||
return new Iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for loading the language string translations.
|
||||
*
|
||||
* @return Language
|
||||
*/
|
||||
public static function language(?string $locale = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('language', $locale)->setLocale($locale);
|
||||
}
|
||||
|
||||
if (AppServices::get('request') instanceof IncomingRequest) {
|
||||
$requestLocale = AppServices::get('request')->getLocale();
|
||||
} else {
|
||||
$requestLocale = Locale::getDefault();
|
||||
}
|
||||
|
||||
// Use '?:' for empty string check
|
||||
$locale = $locale !== null && $locale !== '' && $locale !== '0' ? $locale : $requestLocale;
|
||||
|
||||
return new Language($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Logger class is a PSR-3 compatible Logging class that supports
|
||||
* multiple handlers that process the actual logging.
|
||||
*
|
||||
* @return Logger
|
||||
*/
|
||||
public static function logger(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('logger');
|
||||
}
|
||||
|
||||
return new Logger(config(LoggerConfig::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate Migration runner.
|
||||
*
|
||||
* @return MigrationRunner
|
||||
*/
|
||||
public static function migrations(?Migrations $config = null, ?ConnectionInterface $db = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('migrations', $config, $db);
|
||||
}
|
||||
|
||||
$config ??= config(Migrations::class);
|
||||
|
||||
return new MigrationRunner($config, $db);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Negotiate class provides the content negotiation features for
|
||||
* working the request to determine correct language, encoding, charset,
|
||||
* and more.
|
||||
*
|
||||
* @return Negotiate
|
||||
*/
|
||||
public static function negotiator(?RequestInterface $request = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('negotiator', $request);
|
||||
}
|
||||
|
||||
$request ??= AppServices::get('request');
|
||||
|
||||
return new Negotiate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ResponseCache.
|
||||
*
|
||||
* @return ResponseCache
|
||||
*/
|
||||
public static function responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('responsecache', $config, $cache);
|
||||
}
|
||||
|
||||
$config ??= config(Cache::class);
|
||||
$cache ??= AppServices::get('cache');
|
||||
|
||||
return new ResponseCache($config, $cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate pagination handler.
|
||||
*
|
||||
* @return Pager
|
||||
*/
|
||||
public static function pager(?PagerConfig $config = null, ?RendererInterface $view = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('pager', $config, $view);
|
||||
}
|
||||
|
||||
$config ??= config(PagerConfig::class);
|
||||
$view ??= AppServices::renderer(null, null, false);
|
||||
|
||||
return new Pager($config, $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Parser is a simple template parser.
|
||||
*
|
||||
* @return Parser
|
||||
*/
|
||||
public static function parser(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('parser', $viewPath, $config);
|
||||
}
|
||||
|
||||
$viewPath = $viewPath !== null && $viewPath !== '' && $viewPath !== '0' ? $viewPath : (new Paths())->viewDirectory;
|
||||
$config ??= config(ViewConfig::class);
|
||||
|
||||
return new Parser($config, $viewPath, AppServices::get('locator'), CI_DEBUG, AppServices::get('logger'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Renderer class is the class that actually displays a file to the user.
|
||||
* The default View class within CodeIgniter is intentionally simple, but this
|
||||
* service could easily be replaced by a template engine if the user needed to.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public static function renderer(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('renderer', $viewPath, $config);
|
||||
}
|
||||
|
||||
$viewPath = $viewPath !== null && $viewPath !== '' && $viewPath !== '0' ? $viewPath : (new Paths())->viewDirectory;
|
||||
$config ??= config(ViewConfig::class);
|
||||
|
||||
return new View($config, $viewPath, AppServices::get('locator'), CI_DEBUG, AppServices::get('logger'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Request object.
|
||||
*
|
||||
* createRequest() injects IncomingRequest or CLIRequest.
|
||||
*
|
||||
* @return CLIRequest|IncomingRequest
|
||||
*
|
||||
* @deprecated The parameter $config and $getShared are deprecated.
|
||||
*/
|
||||
public static function request(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('request', $config);
|
||||
}
|
||||
|
||||
// @TODO remove the following code for backward compatibility
|
||||
return AppServices::incomingrequest($config, $getShared);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the current Request object, either IncomingRequest or CLIRequest.
|
||||
*
|
||||
* This method is called from CodeIgniter::getRequestObject().
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function createRequest(App $config, bool $isCli = false): void
|
||||
{
|
||||
if ($isCli) {
|
||||
$request = AppServices::clirequest($config);
|
||||
} else {
|
||||
$request = AppServices::incomingrequest($config);
|
||||
|
||||
// guess at protocol if needed
|
||||
$request->setProtocolVersion($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1');
|
||||
}
|
||||
|
||||
// Inject the request object into Services.
|
||||
static::$instances['request'] = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IncomingRequest class models an HTTP request.
|
||||
*
|
||||
* @return IncomingRequest
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function incomingrequest(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('request', $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
|
||||
return new IncomingRequest(
|
||||
$config,
|
||||
AppServices::get('uri'),
|
||||
'php://input',
|
||||
new UserAgent(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Response class models an HTTP response.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function response(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('response', $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
|
||||
return new Response($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Redirect class provides nice way of working with redirects.
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public static function redirectresponse(?App $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('redirectresponse', $config);
|
||||
}
|
||||
|
||||
$config ??= config(App::class);
|
||||
$response = new RedirectResponse($config);
|
||||
$response->setProtocolVersion(AppServices::get('request')->getProtocolVersion());
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Routes service is a class that allows for easily building
|
||||
* a collection of routes.
|
||||
*
|
||||
* @return RouteCollection
|
||||
*/
|
||||
public static function routes(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('routes');
|
||||
}
|
||||
|
||||
return new RouteCollection(AppServices::get('locator'), new Modules(), config(Routing::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Router class uses a RouteCollection's array of routes, and determines
|
||||
* the correct Controller and Method to execute.
|
||||
*
|
||||
* @return Router
|
||||
*/
|
||||
public static function router(?RouteCollectionInterface $routes = null, ?Request $request = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('router', $routes, $request);
|
||||
}
|
||||
|
||||
$routes ??= AppServices::get('routes');
|
||||
$request ??= AppServices::get('request');
|
||||
|
||||
return new Router($routes, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Security class provides a few handy tools for keeping the site
|
||||
* secure, most notably the CSRF protection tools.
|
||||
*
|
||||
* @return Security
|
||||
*/
|
||||
public static function security(?SecurityConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('security', $config);
|
||||
}
|
||||
|
||||
$config ??= config(SecurityConfig::class);
|
||||
|
||||
return new Security($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the session manager.
|
||||
*
|
||||
* @return Session
|
||||
*/
|
||||
public static function session(?SessionConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('session', $config);
|
||||
}
|
||||
|
||||
$config ??= config(SessionConfig::class);
|
||||
|
||||
$logger = AppServices::get('logger');
|
||||
|
||||
$driverName = $config->driver;
|
||||
|
||||
if ($driverName === DatabaseHandler::class) {
|
||||
$DBGroup = $config->DBGroup ?? config(Database::class)->defaultGroup;
|
||||
|
||||
$driverPlatform = Database::connect($DBGroup)->getPlatform();
|
||||
|
||||
if ($driverPlatform === 'MySQLi') {
|
||||
$driverName = MySQLiHandler::class;
|
||||
} elseif ($driverPlatform === 'Postgre') {
|
||||
$driverName = PostgreHandler::class;
|
||||
}
|
||||
}
|
||||
|
||||
if (! class_exists($driverName) || ! is_a($driverName, SessionBaseHandler::class, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid session handler "%s" provided.',
|
||||
$driverName,
|
||||
));
|
||||
}
|
||||
|
||||
/** @var SessionBaseHandler $driver */
|
||||
$driver = new $driverName($config, AppServices::get('request')->getIPAddress());
|
||||
$driver->setLogger($logger);
|
||||
|
||||
$session = new Session($driver, $config);
|
||||
$session->setLogger($logger);
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// PHP Session emits the headers according to `session.cache_limiter`.
|
||||
// See https://www.php.net/manual/en/function.session-cache-limiter.php.
|
||||
// The headers are not managed by CI's Response class.
|
||||
// So, we remove CI's default Cache-Control header.
|
||||
AppServices::get('response')->removeHeader('Cache-Control');
|
||||
|
||||
$session->start();
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Factory for SiteURI.
|
||||
*
|
||||
* @return SiteURIFactory
|
||||
*/
|
||||
public static function siteurifactory(
|
||||
?App $config = null,
|
||||
?Superglobals $superglobals = null,
|
||||
bool $getShared = true,
|
||||
) {
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('siteurifactory', $config, $superglobals);
|
||||
}
|
||||
|
||||
$config ??= config('App');
|
||||
$superglobals ??= AppServices::get('superglobals');
|
||||
|
||||
return new SiteURIFactory($config, $superglobals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Superglobals.
|
||||
*
|
||||
* @return Superglobals
|
||||
*/
|
||||
public static function superglobals(
|
||||
?array $server = null,
|
||||
?array $get = null,
|
||||
bool $getShared = true,
|
||||
) {
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('superglobals', $server, $get);
|
||||
}
|
||||
|
||||
return new Superglobals($server, $get);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Throttler class provides a simple method for implementing
|
||||
* rate limiting in your applications.
|
||||
*
|
||||
* @return Throttler
|
||||
*/
|
||||
public static function throttler(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('throttler');
|
||||
}
|
||||
|
||||
return new Throttler(AppServices::get('cache'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Timer class provides a simple way to Benchmark portions of your
|
||||
* application.
|
||||
*
|
||||
* @return Timer
|
||||
*/
|
||||
public static function timer(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('timer');
|
||||
}
|
||||
|
||||
return new Timer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the debug toolbar.
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
public static function toolbar(?ToolbarConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('toolbar', $config);
|
||||
}
|
||||
|
||||
$config ??= config(ToolbarConfig::class);
|
||||
|
||||
return new Toolbar($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI class provides a way to model and manipulate URIs.
|
||||
*
|
||||
* @param string|null $uri The URI string
|
||||
*
|
||||
* @return URI The current URI if $uri is null.
|
||||
*/
|
||||
public static function uri(?string $uri = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('uri', $uri);
|
||||
}
|
||||
|
||||
if ($uri === null) {
|
||||
$appConfig = config(App::class);
|
||||
$factory = AppServices::siteurifactory($appConfig, AppServices::get('superglobals'));
|
||||
|
||||
return $factory->createFromGlobals();
|
||||
}
|
||||
|
||||
return new URI($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Validation class provides tools for validating input data.
|
||||
*
|
||||
* @return ValidationInterface
|
||||
*/
|
||||
public static function validation(?ValidationConfig $config = null, bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('validation', $config);
|
||||
}
|
||||
|
||||
$config ??= config(ValidationConfig::class);
|
||||
|
||||
return new Validation($config, AppServices::get('renderer'));
|
||||
}
|
||||
|
||||
/**
|
||||
* View cells are intended to let you insert HTML into view
|
||||
* that has been generated by any callable in the system.
|
||||
*
|
||||
* @return Cell
|
||||
*/
|
||||
public static function viewcell(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('viewcell');
|
||||
}
|
||||
|
||||
return new Cell(AppServices::get('cache'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Typography class provides a way to format text in semantically relevant ways.
|
||||
*
|
||||
* @return Typography
|
||||
*/
|
||||
public static function typography(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('typography');
|
||||
}
|
||||
|
||||
return new Typography();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?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\Config;
|
||||
|
||||
use CodeIgniter\View\ViewDecoratorInterface;
|
||||
|
||||
/**
|
||||
* View configuration
|
||||
*
|
||||
* @phpstan-type parser_callable (callable(mixed): mixed)
|
||||
* @phpstan-type parser_callable_string (callable(mixed): mixed)&string
|
||||
*/
|
||||
class View extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* When false, the view method will clear the data between each
|
||||
* call.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $saveData = true;
|
||||
|
||||
/**
|
||||
* Parser Filters map a filter name with any PHP callable. When the
|
||||
* Parser prepares a variable for display, it will chain it
|
||||
* through the filters in the order defined, inserting any parameters.
|
||||
*
|
||||
* To prevent potential abuse, all filters MUST be defined here
|
||||
* in order for them to be available for use within the Parser.
|
||||
*
|
||||
* @psalm-suppress UndefinedDocblockClass
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @phpstan-var array<string, parser_callable_string>
|
||||
*/
|
||||
public $filters = [];
|
||||
|
||||
/**
|
||||
* Parser Plugins provide a way to extend the functionality provided
|
||||
* by the core Parser by creating aliases that will be replaced with
|
||||
* any callable. Can be single or tag pair.
|
||||
*
|
||||
* @psalm-suppress UndefinedDocblockClass
|
||||
*
|
||||
* @var array<string, callable|list<string>|string>
|
||||
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
|
||||
*/
|
||||
public $plugins = [];
|
||||
|
||||
/**
|
||||
* Built-in View filters.
|
||||
*
|
||||
* @psalm-suppress UndefinedDocblockClass
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @phpstan-var array<string, parser_callable_string>
|
||||
*/
|
||||
protected $coreFilters = [
|
||||
'abs' => '\abs',
|
||||
'capitalize' => '\CodeIgniter\View\Filters::capitalize',
|
||||
'date' => '\CodeIgniter\View\Filters::date',
|
||||
'date_modify' => '\CodeIgniter\View\Filters::date_modify',
|
||||
'default' => '\CodeIgniter\View\Filters::default',
|
||||
'esc' => '\CodeIgniter\View\Filters::esc',
|
||||
'excerpt' => '\CodeIgniter\View\Filters::excerpt',
|
||||
'highlight' => '\CodeIgniter\View\Filters::highlight',
|
||||
'highlight_code' => '\CodeIgniter\View\Filters::highlight_code',
|
||||
'limit_words' => '\CodeIgniter\View\Filters::limit_words',
|
||||
'limit_chars' => '\CodeIgniter\View\Filters::limit_chars',
|
||||
'local_currency' => '\CodeIgniter\View\Filters::local_currency',
|
||||
'local_number' => '\CodeIgniter\View\Filters::local_number',
|
||||
'lower' => '\strtolower',
|
||||
'nl2br' => '\CodeIgniter\View\Filters::nl2br',
|
||||
'number_format' => '\number_format',
|
||||
'prose' => '\CodeIgniter\View\Filters::prose',
|
||||
'round' => '\CodeIgniter\View\Filters::round',
|
||||
'strip_tags' => '\strip_tags',
|
||||
'title' => '\CodeIgniter\View\Filters::title',
|
||||
'upper' => '\strtoupper',
|
||||
];
|
||||
|
||||
/**
|
||||
* Built-in View plugins.
|
||||
*
|
||||
* @psalm-suppress UndefinedDocblockClass
|
||||
*
|
||||
* @var array<string, callable|list<string>|string>
|
||||
* @phpstan-var array<string, array<parser_callable_string>|parser_callable_string|parser_callable>
|
||||
*/
|
||||
protected $corePlugins = [
|
||||
'csp_script_nonce' => '\CodeIgniter\View\Plugins::cspScriptNonce',
|
||||
'csp_style_nonce' => '\CodeIgniter\View\Plugins::cspStyleNonce',
|
||||
'current_url' => '\CodeIgniter\View\Plugins::currentURL',
|
||||
'previous_url' => '\CodeIgniter\View\Plugins::previousURL',
|
||||
'mailto' => '\CodeIgniter\View\Plugins::mailto',
|
||||
'safe_mailto' => '\CodeIgniter\View\Plugins::safeMailto',
|
||||
'lang' => '\CodeIgniter\View\Plugins::lang',
|
||||
'validation_errors' => '\CodeIgniter\View\Plugins::validationErrors',
|
||||
'route' => '\CodeIgniter\View\Plugins::route',
|
||||
'siteURL' => '\CodeIgniter\View\Plugins::siteURL',
|
||||
];
|
||||
|
||||
/**
|
||||
* View Decorators are class methods that will be run in sequence to
|
||||
* have a chance to alter the generated output just prior to caching
|
||||
* the results.
|
||||
*
|
||||
* All classes must implement CodeIgniter\View\ViewDecoratorInterface
|
||||
*
|
||||
* @var list<class-string<ViewDecoratorInterface>>
|
||||
*/
|
||||
public array $decorators = [];
|
||||
|
||||
/**
|
||||
* Merge the built-in and developer-configured filters and plugins,
|
||||
* with preference to the developer ones.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->filters = array_merge($this->coreFilters, $this->filters);
|
||||
$this->plugins = array_merge($this->corePlugins, $this->plugins);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user