This commit is contained in:
Sebastian Molenda
2026-05-12 21:10:38 +02:00
commit ab96d82fcf
2544 changed files with 721700 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Core\Exception\CakeException;
/**
* App is responsible for resource location, and path management.
*
* ### Adding paths
*
* Additional paths for Templates and Plugins are configured with Configure now. See config/app.php for an
* example. The `App.paths.plugins` and `App.paths.templates` variables are used to configure paths for plugins
* and templates respectively. All class based resources should be mapped using your application's autoloader.
*
* ### Inspecting loaded paths
*
* You can inspect the currently loaded paths using `App::classPath('Controller')` for example to see loaded
* controller paths.
*
* It is also possible to inspect paths for plugin classes, for instance, to get
* the path to a plugin's helpers you would call `App::classPath('View/Helper', 'MyPlugin')`
*
* ### Locating plugins
*
* Plugins can be located with App as well. Using Plugin::path('DebugKit') for example, will
* give you the full path to the DebugKit plugin.
*
* @link https://book.cakephp.org/5/en/core-libraries/app.html
*/
class App
{
/**
* Return the class name namespaced. This method checks if the class is defined on the
* application/plugin, otherwise try to load from the CakePHP core
*
* @param string $class Class name
* @param string $type Type of class
* @param string $suffix Class name suffix
* @return class-string|null Namespaced class name, null if the class is not found.
*/
public static function className(string $class, string $type = '', string $suffix = ''): ?string
{
if (str_contains($class, '\\')) {
return class_exists($class) ? $class : null;
}
[$plugin, $name] = pluginSplit($class);
$fullname = '\\' . str_replace('/', '\\', $type . '\\' . $name) . $suffix;
$base = $plugin ?: Configure::read('App.namespace');
if ($base !== null) {
$base = str_replace('/', '\\', rtrim($base, '\\'));
if (static::_classExistsInBase($fullname, $base)) {
/** @var class-string */
return $base . $fullname;
}
}
if ($plugin || !static::_classExistsInBase($fullname, 'Cake')) {
return null;
}
/** @var class-string */
return 'Cake' . $fullname;
}
/**
* Returns the plugin split name of a class
*
* Examples:
*
* ```
* App::shortName(
* 'SomeVendor\SomePlugin\Controller\Component\TestComponent',
* 'Controller/Component',
* 'Component'
* )
* ```
*
* Returns: SomeVendor/SomePlugin.Test
*
* ```
* App::shortName(
* 'SomeVendor\SomePlugin\Controller\Component\Subfolder\TestComponent',
* 'Controller/Component',
* 'Component'
* )
* ```
*
* Returns: SomeVendor/SomePlugin.Subfolder/Test
*
* ```
* App::shortName(
* 'Cake\Controller\Component\FlashComponent',
* 'Controller/Component',
* 'Component'
* )
* ```
*
* Returns: Flash
*
* @param string $class Class name
* @param string $type Type of class
* @param string $suffix Class name suffix
* @return string Plugin split name of class
*/
public static function shortName(string $class, string $type, string $suffix = ''): string
{
$class = str_replace('\\', '/', $class);
$type = '/' . $type . '/';
$pos = strrpos($class, $type);
if ($pos === false) {
return $class;
}
$pluginName = substr($class, 0, $pos);
$name = substr($class, $pos + strlen($type));
if ($suffix) {
$name = substr($name, 0, -strlen($suffix));
}
$nonPluginNamespaces = [
'Cake',
str_replace('\\', '/', (string)Configure::read('App.namespace')),
];
if (in_array($pluginName, $nonPluginNamespaces, true)) {
return $name;
}
return $pluginName . '.' . $name;
}
/**
* _classExistsInBase
*
* Test isolation wrapper
*
* @param string $name Class name.
* @param string $namespace Namespace.
* @return bool
*/
protected static function _classExistsInBase(string $name, string $namespace): bool
{
return class_exists($namespace . $name);
}
/**
* Used to read information of stored path.
*
* When called without the `$plugin` argument it will return the value of `App.paths.$type` config.
*
* Default types:
* - plugins
* - templates
* - locales
*
* Example:
*
* ```
* App::path('plugins');
* ```
*
* Will return the value of `App.paths.plugins` config.
*
* For plugins it can be used to get paths for types `templates` or `locales`.
*
* @param string $type Type of path
* @param string|null $plugin Plugin name
* @return array<int|string, string>
* @link https://book.cakephp.org/5/en/core-libraries/app.html#finding-paths-to-namespaces
*/
public static function path(string $type, ?string $plugin = null): array
{
if ($plugin === null) {
return (array)Configure::read('App.paths.' . $type);
}
return match ($type) {
'templates' => [Plugin::templatePath($plugin)],
'locales' => [Plugin::path($plugin) . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR],
default => throw new CakeException(sprintf(
'Invalid type `%s`. Only path types `templates` and `locales` are supported for plugins.',
$type,
))
};
}
/**
* Gets the path to a class type in the application or a plugin.
*
* Example:
*
* ```
* App::classPath('Model/Table');
* ```
*
* Will return the path for tables - e.g. `src/Model/Table/`.
*
* ```
* App::classPath('Model/Table', 'My/Plugin');
* ```
*
* Will return the plugin based path for those.
*
* @param string $type Package type.
* @param string|null $plugin Plugin name.
* @return array<string>
*/
public static function classPath(string $type, ?string $plugin = null): array
{
if ($plugin !== null) {
return [
Plugin::classPath($plugin) . $type . DIRECTORY_SEPARATOR,
];
}
return [APP . $type . DIRECTORY_SEPARATOR];
}
/**
* Returns the full path to a package inside the CakePHP core
*
* Usage:
*
* ```
* App::core('Cache/Engine');
* ```
*
* Will return the full path to the cache engines package.
*
* @param string $type Package type.
* @return array<string> Full path to package
*/
public static function core(string $type): array
{
if ($type === 'templates') {
return [CORE_PATH . 'templates' . DIRECTORY_SEPARATOR];
}
return [CAKE . str_replace('/', DIRECTORY_SEPARATOR, $type) . DIRECTORY_SEPARATOR];
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 5.3.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Attribute;
use Attribute;
use Cake\Core\Configure as CakeConfigure;
use League\Container\Attribute\AttributeInterface;
/**
* Configure attribute for dependency injection container delegate.
*
* This provides autowiring config data into constructors when delegate is enabled.
*
* Example:
* ```
* <?php
* declare(strict_types=1);
*
* namespace App\Model\WebService;
* use Cake\Core\Attribute\Configure;
*
* class CustomClient
* {
* public function __construct(
* #[Configure('CustomService.apiKey')] protected string $apiKey,
* ) { }
* }
* ```
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
class Configure implements AttributeInterface
{
/**
* @param string $name
*/
public function __construct(private string $name)
{
}
/**
* @return mixed
*/
public function resolve(): mixed
{
return CakeConfigure::read($this->name);
}
}
+324
View File
@@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Console\CommandCollection;
use Cake\Event\EventManagerInterface;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
use Closure;
use InvalidArgumentException;
use ReflectionClass;
/**
* Base Plugin Class
*
* Every plugin should extend from this class or implement the interfaces and
* include a plugin class in its src root folder.
*/
class BasePlugin implements PluginInterface
{
/**
* Do bootstrapping or not
*
* @var bool
*/
protected bool $bootstrapEnabled = true;
/**
* Console middleware
*
* @var bool
*/
protected bool $consoleEnabled = true;
/**
* Enable middleware
*
* @var bool
*/
protected bool $middlewareEnabled = true;
/**
* Register container services
*
* @var bool
*/
protected bool $servicesEnabled = true;
/**
* Load routes or not
*
* @var bool
*/
protected bool $routesEnabled = true;
/**
* Load events or not
*
* @var bool
*/
protected bool $eventsEnabled = true;
/**
* The path to this plugin.
*
* @var string|null
*/
protected ?string $path = null;
/**
* The class path for this plugin.
*
* @var string|null
*/
protected ?string $classPath = null;
/**
* The config path for this plugin.
*
* @var string|null
*/
protected ?string $configPath = null;
/**
* The templates path for this plugin.
*
* @var string|null
*/
protected ?string $templatePath = null;
/**
* The name of this plugin
*
* @var string|null
*/
protected ?string $name = null;
/**
* Constructor
*
* @param array<string, mixed> $options Options
*/
public function __construct(array $options = [])
{
foreach (static::VALID_HOOKS as $key) {
if (isset($options[$key])) {
$this->{"{$key}Enabled"} = (bool)$options[$key];
}
}
foreach (['name', 'path', 'classPath', 'configPath', 'templatePath'] as $path) {
if (isset($options[$path])) {
$this->{$path} = $options[$path];
}
}
$this->initialize();
}
/**
* Initialization hook called from constructor.
*
* @return void
*/
public function initialize(): void
{
}
/**
* @inheritDoc
*/
public function getName(): string
{
if ($this->name !== null) {
return $this->name;
}
$parts = explode('\\', static::class);
array_pop($parts);
return $this->name = implode('/', $parts);
}
/**
* @inheritDoc
*/
public function getPath(): string
{
if ($this->path !== null) {
return $this->path;
}
$reflection = new ReflectionClass($this);
$path = dirname((string)$reflection->getFileName());
// Trim off src
if (str_ends_with($path, 'src')) {
$path = substr($path, 0, -3);
}
return $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
/**
* @inheritDoc
*/
public function getConfigPath(): string
{
if ($this->configPath !== null) {
return $this->configPath;
}
$path = $this->getPath();
return $path . 'config' . DIRECTORY_SEPARATOR;
}
/**
* @inheritDoc
*/
public function getClassPath(): string
{
if ($this->classPath !== null) {
return $this->classPath;
}
$path = $this->getPath();
return $path . 'src' . DIRECTORY_SEPARATOR;
}
/**
* @inheritDoc
*/
public function getTemplatePath(): string
{
if ($this->templatePath !== null) {
return $this->templatePath;
}
$path = $this->getPath();
return $this->templatePath = $path . 'templates' . DIRECTORY_SEPARATOR;
}
/**
* @inheritDoc
*/
public function enable(string $hook)
{
$this->checkHook($hook);
$this->{"{$hook}Enabled"} = true;
return $this;
}
/**
* @inheritDoc
*/
public function disable(string $hook)
{
$this->checkHook($hook);
$this->{"{$hook}Enabled"} = false;
return $this;
}
/**
* @inheritDoc
*/
public function isEnabled(string $hook): bool
{
$this->checkHook($hook);
return $this->{"{$hook}Enabled"} === true;
}
/**
* Check if a hook name is valid
*
* @param string $hook The hook name to check
* @throws \InvalidArgumentException on invalid hooks
* @return void
*/
protected function checkHook(string $hook): void
{
if (!in_array($hook, static::VALID_HOOKS, true)) {
throw new InvalidArgumentException(sprintf(
'`%s` is not a valid hook name. Must be one of `%s.`',
$hook,
implode(', ', static::VALID_HOOKS),
));
}
}
/**
* @inheritDoc
*/
public function routes(RouteBuilder $routes): void
{
$path = $this->getConfigPath() . 'routes.php';
if (is_file($path)) {
$return = require $path;
if ($return instanceof Closure) {
$return($routes);
}
}
}
/**
* @inheritDoc
*/
public function bootstrap(PluginApplicationInterface $app): void
{
$bootstrap = $this->getConfigPath() . 'bootstrap.php';
if (is_file($bootstrap)) {
require $bootstrap;
}
}
/**
* @inheritDoc
*/
public function console(CommandCollection $commands): CommandCollection
{
return $commands->addMany($commands->discoverPlugin($this->getName()));
}
/**
* @inheritDoc
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
return $middlewareQueue;
}
/**
* Register container services for this plugin.
*
* @param \Cake\Core\ContainerInterface $container The container to add services to.
* @return void
*/
public function services(ContainerInterface $container): void
{
}
/**
* Register application events.
*
* @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on
* @return \Cake\Event\EventManagerInterface
*/
public function events(EventManagerInterface $eventManager): EventManagerInterface
{
return $eventManager;
}
}
+493
View File
@@ -0,0 +1,493 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Cache\Cache;
use Cake\Core\Configure\ConfigEngineInterface;
use Cake\Core\Configure\Engine\PhpConfig;
use Cake\Core\Exception\CakeException;
use Cake\Utility\Hash;
/**
* Configuration class. Used for managing runtime configuration information.
*
* Provides features for reading and writing to the runtime configuration, as well
* as methods for loading additional configuration files or storing runtime configuration
* for future use.
*
* @link https://book.cakephp.org/5/en/development/configuration.html
*/
class Configure
{
/**
* Array of values currently stored in Configure.
*
* @var array<string, mixed>
*/
protected static array $_values = [
'debug' => false,
];
/**
* Configured engine classes, used to load config files from resources
*
* @see \Cake\Core\Configure::load()
* @var array<\Cake\Core\Configure\ConfigEngineInterface>
*/
protected static array $_engines = [];
/**
* Flag to track whether ini_set exists.
*
* @var bool|null
*/
protected static ?bool $_hasIniSet = null;
/**
* Used to store a dynamic variable in Configure.
*
* Usage:
* ```
* Configure::write('One.key1', 'value of the Configure::One[key1]');
* Configure::write(['One.key1' => 'value of the Configure::One[key1]']);
* Configure::write('One', [
* 'key1' => 'value of the Configure::One[key1]',
* 'key2' => 'value of the Configure::One[key2]'
* ]);
*
* Configure::write([
* 'One.key1' => 'value of the Configure::One[key1]',
* 'One.key2' => 'value of the Configure::One[key2]'
* ]);
* ```
*
* @param array<string, mixed>|string $config The key to write, can be a dot notation value.
* Alternatively can be an array containing key(s) and value(s).
* @param mixed $value Value to set for the given key.
* @return void
* @link https://book.cakephp.org/5/en/development/configuration.html#writing-configuration-data
*/
public static function write(array|string $config, mixed $value = null): void
{
if (!is_array($config)) {
$config = [$config => $value];
}
foreach ($config as $name => $valueToInsert) {
static::$_values = Hash::insert(static::$_values, $name, $valueToInsert);
}
if (isset($config['debug'])) {
static::$_hasIniSet ??= function_exists('ini_set');
if (static::$_hasIniSet) {
ini_set('display_errors', $config['debug'] ? '1' : '0');
}
}
}
/**
* Used to read information stored in Configure. It's not
* possible to store `null` values in Configure.
*
* Usage:
* ```
* Configure::read('Name'); will return all values for Name
* Configure::read('Name.key'); will return only the value of Configure::Name[key]
* ```
*
* @param string|null $var Variable to obtain. Use '.' to access array elements.
* @param mixed $default The return value when the configure does not exist
* @return mixed Value stored in configure, or null.
* @link https://book.cakephp.org/5/en/development/configuration.html#reading-configuration-data
*/
public static function read(?string $var = null, mixed $default = null): mixed
{
if ($var === null) {
return static::$_values;
}
return Hash::get(static::$_values, $var, $default);
}
/**
* Returns true if given variable is set in Configure.
*
* @param string $var Variable name to check for
* @return bool True if variable is there
*/
public static function check(string $var): bool
{
if (!$var) {
return false;
}
return static::read($var) !== null;
}
/**
* Used to get information stored in Configure. It's not
* possible to store `null` values in Configure.
*
* Acts as a wrapper around Configure::read() and Configure::check().
* The configure key/value pair fetched via this method is expected to exist.
* In case it does not an exception will be thrown.
*
* Usage:
* ```
* Configure::readOrFail('Name'); will return all values for Name
* Configure::readOrFail('Name.key'); will return only the value of Configure::Name[key]
* ```
*
* @param string $var Variable to obtain. Use '.' to access array elements.
* @return mixed Value stored in configure.
* @throws \Cake\Core\Exception\CakeException if the requested configuration is not set.
* @link https://book.cakephp.org/5/en/development/configuration.html#reading-configuration-data
*/
public static function readOrFail(string $var): mixed
{
if (!static::check($var)) {
throw new CakeException(sprintf('Expected configuration key `%s` not found.', $var));
}
return static::read($var);
}
/**
* Used to delete a variable from Configure.
*
* Usage:
* ```
* Configure::delete('Name'); will delete the entire Configure::Name
* Configure::delete('Name.key'); will delete only the Configure::Name[key]
* ```
*
* @param string $var the var to be deleted
* @return void
* @link https://book.cakephp.org/5/en/development/configuration.html#deleting-configuration-data
*/
public static function delete(string $var): void
{
static::$_values = Hash::remove(static::$_values, $var);
}
/**
* Used to consume information stored in Configure. It's not
* possible to store `null` values in Configure.
*
* Acts as a wrapper around Configure::consume() and Configure::check().
* The configure key/value pair consumed via this method is expected to exist.
* In case it does not an exception will be thrown.
*
* @param string $var Variable to consume. Use '.' to access array elements.
* @return mixed Value stored in configure.
* @throws \Cake\Core\Exception\CakeException if the requested configuration is not set.
* @since 3.6.0
*/
public static function consumeOrFail(string $var): mixed
{
if (!static::check($var)) {
throw new CakeException(sprintf('Expected configuration key `%s` not found.', $var));
}
return static::consume($var);
}
/**
* Used to read and delete a variable from Configure.
*
* This is primarily used during bootstrapping to move configuration data
* out of configure into the various other classes in CakePHP.
*
* @param string $var The key to read and remove.
* @return mixed The value stored in Configure, or null if the key doesn't exist.
*/
public static function consume(string $var): mixed
{
if (!str_contains($var, '.')) {
if (!isset(static::$_values[$var])) {
return null;
}
$value = static::$_values[$var];
unset(static::$_values[$var]);
return $value;
}
$value = Hash::get(static::$_values, $var);
static::delete($var);
return $value;
}
/**
* Add a new engine to Configure. Engines allow you to read configuration
* files in various formats/storage locations. CakePHP comes with two built-in engines
* PhpConfig and IniConfig. You can also implement your own engine classes in your application.
*
* To add a new engine to Configure:
*
* ```
* Configure::config('ini', new IniConfig());
* ```
*
* @param string $name The name of the engine being configured. This alias is used later to
* read values from a specific engine.
* @param \Cake\Core\Configure\ConfigEngineInterface $engine The engine to append.
* @return void
*/
public static function config(string $name, ConfigEngineInterface $engine): void
{
static::$_engines[$name] = $engine;
}
/**
* Returns true if the Engine objects is configured.
*
* @param string $name Engine name.
* @return bool
*/
public static function isConfigured(string $name): bool
{
return isset(static::$_engines[$name]);
}
/**
* Gets the names of the configured Engine objects.
*
* @return array<string>
*/
public static function configured(): array
{
$engines = array_keys(static::$_engines);
return array_map(function (int|string $key) {
return (string)$key;
}, $engines);
}
/**
* Remove a configured engine. This will unset the engine
* and make any future attempts to use it cause an Exception.
*
* @param string $name Name of the engine to drop.
* @return bool Success
*/
public static function drop(string $name): bool
{
if (!isset(static::$_engines[$name])) {
return false;
}
unset(static::$_engines[$name]);
return true;
}
/**
* Loads stored configuration information from a resource. You can add
* config file resource engines with `Configure::config()`.
*
* Loaded configuration information will be merged with the current
* runtime configuration. You can load configuration files from plugins
* by preceding the filename with the plugin name.
*
* `Configure::load('Users.user', 'default')`
*
* Would load the 'user' config file using the default config engine. You can load
* app config files by giving the name of the resource you want loaded.
*
* ```
* Configure::load('setup', 'default');
* ```
*
* If using `default` config and no engine has been configured for it yet,
* one will be automatically created using PhpConfig
*
* @param string $key name of configuration resource to load.
* @param string $config Name of the configured engine to use to read the resource identified by $key.
* @param bool $merge if config files should be merged instead of simply overridden
* @return bool True if load successful.
* @throws \Cake\Core\Exception\CakeException if the $config engine is not found
* @link https://book.cakephp.org/5/en/development/configuration.html#reading-and-writing-configuration-files
*/
public static function load(string $key, string $config = 'default', bool $merge = true): bool
{
$engine = static::_getEngine($config);
if (!$engine) {
throw new CakeException(
sprintf(
'Config %s engine not found when attempting to load %s.',
$config,
$key,
),
);
}
$values = $engine->read($key);
if ($merge) {
$values = Hash::merge(static::$_values, $values);
}
static::write($values);
return true;
}
/**
* Dump data currently in Configure into $key. The serialization format
* is decided by the config engine attached as $config. For example, if the
* 'default' adapter is a PhpConfig, the generated file will be a PHP
* configuration file loadable by the PhpConfig.
*
* ### Usage
*
* Given that the 'default' engine is an instance of PhpConfig.
* Save all data in Configure to the file `my_config.php`:
*
* ```
* Configure::dump('my_config', 'default');
* ```
*
* Save only the error handling configuration:
*
* ```
* Configure::dump('error', 'default', ['Error', 'Exception'];
* ```
*
* @param string $key The identifier to create in the config adapter.
* This could be a filename or a cache key depending on the adapter being used.
* @param string $config The name of the configured adapter to dump data with.
* @param array<string> $keys The name of the top-level keys you want to dump.
* This allows you save only some data stored in Configure.
* @return bool Success
* @throws \Cake\Core\Exception\CakeException if the adapter does not implement a `dump` method.
*/
public static function dump(string $key, string $config = 'default', array $keys = []): bool
{
$engine = static::_getEngine($config);
if (!$engine) {
throw new CakeException(sprintf('There is no `%s` config engine.', $config));
}
$values = static::$_values;
if ($keys) {
$values = array_intersect_key($values, array_flip($keys));
}
return $engine->dump($key, $values);
}
/**
* Get the configured engine. Internally used by `Configure::load()` and `Configure::dump()`
* Will create new PhpConfig for default if not configured yet.
*
* @param string $config The name of the configured adapter
* @return \Cake\Core\Configure\ConfigEngineInterface|null Engine instance or null
*/
protected static function _getEngine(string $config): ?ConfigEngineInterface
{
if (!isset(static::$_engines[$config])) {
if ($config !== 'default') {
return null;
}
static::config($config, new PhpConfig());
}
return static::$_engines[$config];
}
/**
* Used to determine the current version of CakePHP.
*
* Usage
* ```
* Configure::version();
* ```
*
* @return string Current version of CakePHP
*/
public static function version(): string
{
$version = static::read('Cake.version');
if ($version !== null) {
return $version;
}
$path = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config/config.php';
if (is_file($path)) {
$config = require $path;
static::write($config);
return static::read('Cake.version');
}
return 'unknown';
}
/**
* Used to write runtime configuration into Cache. Stored runtime configuration can be
* restored using `Configure::restore()`. These methods can be used to enable configuration managers
* frontends, or other GUI type interfaces for configuration.
*
* @param string $name The storage name for the saved configuration.
* @param string $cacheConfig The cache configuration to save into. Defaults to 'default'
* @param array|null $data Either an array of data to store, or leave empty to store all values.
* @return bool Success
*/
public static function store(string $name, string $cacheConfig = 'default', ?array $data = null): bool
{
$data ??= static::$_values;
if (!class_exists(Cache::class)) {
throw new CakeException('You must install cakephp/cache to use Configure::store()');
}
return Cache::write($name, $data, $cacheConfig);
}
/**
* Restores configuration data stored in the Cache into configure. Restored
* values will overwrite existing ones.
*
* @param string $name Name of the stored config file to load.
* @param string $cacheConfig Name of the Cache configuration to read from.
* @return bool Success.
*/
public static function restore(string $name, string $cacheConfig = 'default'): bool
{
if (!class_exists(Cache::class)) {
throw new CakeException('You must install cakephp/cache to use Configure::restore()');
}
$values = Cache::read($name, $cacheConfig);
if ($values) {
static::write($values);
return true;
}
return false;
}
/**
* Clear all values stored in Configure.
*
* @return void
*/
public static function clear(): void
{
static::$_values = [];
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Configure;
/**
* An interface for creating objects compatible with Configure::load()
*/
interface ConfigEngineInterface
{
/**
* Read a configuration file/storage key
*
* This method is used for reading configuration information from sources.
* These sources can either be static resources like files, or dynamic ones like
* a database, or other datasource.
*
* @param string $key Key to read.
* @return array An array of data to merge into the runtime configuration
*/
public function read(string $key): array;
/**
* Dumps the configure data into the storage key/file of the given `$key`.
*
* @param string $key The identifier to write to.
* @param array $data The data to dump.
* @return bool True on success or false on failure.
*/
public function dump(string $key, array $data): bool;
}
@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Configure\Engine;
use Cake\Core\Configure\ConfigEngineInterface;
use Cake\Core\Configure\FileConfigTrait;
use Cake\Core\Exception\CakeException;
use Cake\Utility\Hash;
/**
* Ini file configuration engine.
*
* Since IniConfig uses parse_ini_file underneath, you should be aware that this
* class shares the same behavior, especially with regards to boolean and null values.
*
* In addition to the native `parse_ini_file` features, IniConfig also allows you
* to create nested array structures through usage of `.` delimited names. This allows
* you to create nested arrays structures in an ini config file. For example:
*
* `db.password = secret` would turn into `['db' => ['password' => 'secret']]`
*
* You can nest properties as deeply as needed using `.`'s. In addition to using `.` you
* can use standard ini section notation to create nested structures:
*
* ```
* [section]
* key = value
* ```
*
* Once loaded into Configure, the above would be accessed using:
*
* `Configure::read('section.key');`
*
* You can also use `.` separated values in section names to create more deeply
* nested structures.
*
* IniConfig also manipulates how the special ini values of
* 'yes', 'no', 'on', 'off', 'null' are handled. These values will be
* converted to their boolean equivalents.
*
* @see https://secure.php.net/parse_ini_file
*/
class IniConfig implements ConfigEngineInterface
{
use FileConfigTrait;
/**
* File extension.
*
* @var string
*/
protected string $_extension = '.ini';
/**
* The section to read, if null all sections will be read.
*
* @var string|null
*/
protected ?string $_section = null;
/**
* Build and construct a new ini file parser. The parser can be used to read
* ini files that are on the filesystem.
*
* @param string|null $path Path to load ini config files from. Defaults to CONFIG.
* @param string|null $section Only get one section, leave null to parse and fetch
* all sections in the ini file.
*/
public function __construct(?string $path = null, ?string $section = null)
{
$this->_path = $path ?? CONFIG;
$this->_section = $section;
}
/**
* Read an ini file and return the results as an array.
*
* @param string $key The identifier to read from. If the key has a . it will be treated
* as a plugin prefix. The chosen file must be on the engine's path.
* @return array Parsed configuration values.
* @throws \Cake\Core\Exception\CakeException when files don't exist.
* Or when files contain '..' as this could lead to abusive reads.
*/
public function read(string $key): array
{
$file = $this->_getFilePath($key, true);
$contents = parse_ini_file($file, true);
if ($contents === false) {
throw new CakeException(sprintf('Cannot parse INI file `%s`', $file));
}
if ($this->_section && isset($contents[$this->_section])) {
$values = $this->_parseNestedValues($contents[$this->_section]);
} else {
$values = [];
foreach ($contents as $section => $attribs) {
if (is_array($attribs)) {
$values[$section] = $this->_parseNestedValues($attribs);
} else {
$parse = $this->_parseNestedValues([$attribs]);
$values[$section] = array_shift($parse);
}
}
}
return $values;
}
/**
* parses nested values out of keys.
*
* @param array $values Values to be exploded.
* @return array Array of values exploded
*/
protected function _parseNestedValues(array $values): array
{
foreach ($values as $key => $value) {
if ($value === '1') {
$value = true;
}
if ($value === '') {
$value = false;
}
unset($values[$key]);
if (str_contains((string)$key, '.')) {
$values = Hash::insert($values, $key, $value);
} else {
$values[$key] = $value;
}
}
return $values;
}
/**
* Dumps the state of Configure data into an ini formatted string.
*
* @param string $key The identifier to write to. If the key has a . it will be treated
* as a plugin prefix.
* @param array $data The data to convert to ini file.
* @return bool Success.
*/
public function dump(string $key, array $data): bool
{
$result = [];
foreach ($data as $k => $value) {
$isSection = false;
if (!str_starts_with($k, '[')) {
$result[] = "[{$k}]";
$isSection = true;
}
if (is_array($value)) {
$kValues = Hash::flatten($value, '.');
foreach ($kValues as $k2 => $v) {
$result[] = "{$k2} = " . $this->_value($v);
}
}
if ($isSection) {
$result[] = '';
}
}
$contents = trim(implode("\n", $result));
$filename = $this->_getFilePath($key);
return file_put_contents($filename, $contents) > 0;
}
/**
* Converts a value into the ini equivalent
*
* @param mixed $value Value to export.
* @return string String value for ini file.
*/
protected function _value(mixed $value): string
{
return match ($value) {
null => 'null',
true => 'true',
false => 'false',
default => (string)$value
};
}
}
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Configure\Engine;
use Cake\Core\Configure\ConfigEngineInterface;
use Cake\Core\Configure\FileConfigTrait;
use Cake\Core\Exception\CakeException;
/**
* JSON engine allows Configure to load configuration values from
* files containing JSON strings.
*
* An example JSON file would look like::
*
* ```
* {
* "debug": false,
* "App": {
* "namespace": "MyApp"
* },
* "Security": {
* "salt": "its-secret"
* }
* }
* ```
*/
class JsonConfig implements ConfigEngineInterface
{
use FileConfigTrait;
/**
* File extension.
*
* @var string
*/
protected string $_extension = '.json';
/**
* Constructor for JSON Config file reading.
*
* @param string|null $path The path to read config files from. Defaults to CONFIG.
*/
public function __construct(?string $path = null)
{
$this->_path = $path ?? CONFIG;
}
/**
* Read a config file and return its contents.
*
* Files with `.` in the name will be treated as values in plugins. Instead of
* reading from the initialized path, plugin keys will be located using Plugin::path().
*
* @param string $key The identifier to read from. If the key has a . it will be treated
* as a plugin prefix.
* @return array Parsed configuration values.
* @throws \Cake\Core\Exception\CakeException When files don't exist or when
* files contain '..' (as this could lead to abusive reads) or when there
* is an error parsing the JSON string.
*/
public function read(string $key): array
{
$file = $this->_getFilePath($key, true);
$jsonContent = file_get_contents($file);
if ($jsonContent === false) {
throw new CakeException(sprintf('Cannot read file content of `%s`', $file));
}
$values = json_decode($jsonContent, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new CakeException(sprintf(
'Error parsing JSON string fetched from config file `%s.json`: %s',
$key,
json_last_error_msg(),
));
}
if (!is_array($values)) {
throw new CakeException(sprintf(
'Decoding JSON config file `%s.json` did not return an array',
$key,
));
}
return $values;
}
/**
* Converts the provided $data into a JSON string that can be used saved
* into a file and loaded later.
*
* @param string $key The identifier to write to. If the key has a . it will
* be treated as a plugin prefix.
* @param array $data Data to dump.
* @return bool Success
*/
public function dump(string $key, array $data): bool
{
$filename = $this->_getFilePath($key);
return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT)) !== false;
}
}
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Configure\Engine;
use Cake\Core\Configure\ConfigEngineInterface;
use Cake\Core\Configure\FileConfigTrait;
use Cake\Core\Exception\CakeException;
/**
* PHP engine allows Configure to load configuration values from
* files containing simple PHP arrays.
*
* Files compatible with PhpConfig should return an array that
* contains all the configuration data contained in the file.
*
* An example configuration file would look like::
*
* ```
* <?php
* return [
* 'debug' => false,
* 'Security' => [
* 'salt' => 'its-secret'
* ],
* 'App' => [
* 'namespace' => 'App'
* ]
* ];
* ```
*
* @see \Cake\Core\Configure::load() for how to load custom configuration files.
*/
class PhpConfig implements ConfigEngineInterface
{
use FileConfigTrait;
/**
* File extension.
*
* @var string
*/
protected string $_extension = '.php';
/**
* Constructor for PHP Config file reading.
*
* @param string|null $path The path to read config files from. Defaults to CONFIG.
*/
public function __construct(?string $path = null)
{
$this->_path = $path ?? CONFIG;
}
/**
* Read a config file and return its contents.
*
* Files with `.` in the name will be treated as values in plugins. Instead of
* reading from the initialized path, plugin keys will be located using Plugin::path().
*
* @param string $key The identifier to read from. If the key has a . it will be treated
* as a plugin prefix.
* @return array Parsed configuration values.
* @throws \Cake\Core\Exception\CakeException when files don't exist or they don't contain `$config`.
* Or when files contain '..' as this could lead to abusive reads.
*/
public function read(string $key): array
{
$file = $this->_getFilePath($key, true);
$return = include $file;
if (is_array($return)) {
return $return;
}
throw new CakeException(sprintf('Config file `%s` did not return an array', $key . '.php.'));
}
/**
* Converts the provided $data into a string of PHP code that can
* be used saved into a file and loaded later.
*
* @param string $key The identifier to write to. If the key has a . it will be treated
* as a plugin prefix.
* @param array $data Data to dump.
* @return bool Success
*/
public function dump(string $key, array $data): bool
{
$contents = '<?php' . "\n" . 'return ' . var_export($data, true) . ';';
$filename = $this->_getFilePath($key);
return file_put_contents($filename, $contents) > 0;
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Configure;
use Cake\Core\Exception\CakeException;
use Cake\Core\Plugin;
use function Cake\Core\pluginSplit;
/**
* Trait providing utility methods for file based config engines.
*/
trait FileConfigTrait
{
/**
* The path this engine finds files on.
*
* @var string
*/
protected string $_path = '';
/**
* Get file path
*
* @param string $key The identifier to write to. If the key has a . it will be treated
* as a plugin prefix.
* @param bool $checkExists Whether to check if file exists. Defaults to false.
* @return string Full file path
* @throws \Cake\Core\Exception\CakeException When files don't exist or when
* files contain '..' as this could lead to abusive reads.
*/
protected function _getFilePath(string $key, bool $checkExists = false): string
{
if (str_contains($key, '..')) {
throw new CakeException('Cannot load/dump configuration files with ../ in them.');
}
[$plugin, $key] = pluginSplit($key);
if ($plugin) {
$file = Plugin::configPath($plugin) . $key;
} else {
$file = $this->_path . $key;
}
$file .= $this->_extension;
if (!$checkExists || is_file($file)) {
return $file;
}
$realPath = realpath($file);
if ($realPath !== false && is_file($realPath)) {
return $realPath;
}
throw new CakeException(sprintf('Could not load configuration file: `%s`.', $file));
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Console\CommandCollection;
/**
* An interface defining the methods that the
* console runner depend on.
*/
interface ConsoleApplicationInterface
{
/**
* Load all the application configuration and bootstrap logic.
*
* Override this method to add additional bootstrap logic for your application.
*
* @return void
*/
public function bootstrap(): void;
/**
* Define the console commands for an application.
*
* @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into.
* @return \Cake\Console\CommandCollection The updated collection.
*/
public function console(CommandCollection $commands): CommandCollection;
}
+28
View File
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use League\Container\Container as LeagueContainer;
/**
* Dependency Injection container
*
* Based on the container out of League\Container
*/
class Container extends LeagueContainer implements ContainerInterface
{
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
/**
* Interface for applications that configure and use a dependency injection container.
*/
interface ContainerApplicationInterface
{
/**
* Register services to the container
*
* Registered services can have instances fetched out of the container
* using `get()`. Dependencies and parameters will be resolved based
* on service definitions.
*
* @param \Cake\Core\ContainerInterface $container The container to add services to
* @return void
*/
public function services(ContainerInterface $container): void;
/**
* Create a new container and register services.
*
* This will `register()` services provided by both the application
* and any plugins if the application has plugin support.
*
* @return \Cake\Core\ContainerInterface A populated container
*/
public function getContainer(): ContainerInterface;
}
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use League\Container\DefinitionContainerInterface;
use Psr\Container\ContainerInterface as PsrContainerInterface;
/**
* Interface for the Dependency Injection Container in CakePHP applications
*
* This interface extends the PSR-11 container interface and adds
* methods to add services and service providers to the container.
*
* The methods defined in this interface use the conventions provided
* by league/container as that is the library that CakePHP uses.
*/
interface ContainerInterface extends DefinitionContainerInterface
{
/**
* @param \Psr\Container\ContainerInterface $container The container instance to use as delegation
* @return \Psr\Container\ContainerInterface
*/
public function delegate(PsrContainerInterface $container): PsrContainerInterface;
}
+156
View File
@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Utility\Inflector;
/**
* Provides methods that allow other classes access to conventions based inflections.
*/
trait ConventionsTrait
{
/**
* Creates a fixture name
*
* @param string $name Model class name
* @return string Singular model key
*/
protected function _fixtureName(string $name): string
{
return Inflector::camelize($name);
}
/**
* Creates the proper entity name (singular) for the specified name
*
* @param string $name Name
* @return string Camelized and plural model name
*/
protected function _entityName(string $name): string
{
return Inflector::singularize(Inflector::camelize($name));
}
/**
* Creates the proper underscored model key for associations
*
* If the input contains a dot, assume that the right side is the real table name.
*
* @param string $name Model class name
* @return string Singular model key
*/
protected function _modelKey(string $name): string
{
[, $name] = pluginSplit($name);
return Inflector::underscore(Inflector::singularize($name)) . '_id';
}
/**
* Creates the proper model name from a foreign key
*
* @param string $key Foreign key
* @return string Model name
*/
protected function _modelNameFromKey(string $key): string
{
$key = str_replace('_id', '', $key);
return Inflector::camelize(Inflector::pluralize($key));
}
/**
* Creates the singular name for use in views.
*
* @param string $name Name to use
* @return string Variable name
*/
protected function _singularName(string $name): string
{
return Inflector::variable(Inflector::singularize($name));
}
/**
* Creates the plural variable name for views
*
* @param string $name Name to use
* @return string Plural name for views
*/
protected function _variableName(string $name): string
{
return Inflector::variable($name);
}
/**
* Creates the singular human name used in views
*
* @param string $name Controller name
* @return string Singular human name
*/
protected function _singularHumanName(string $name): string
{
return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
}
/**
* Creates a camelized version of $name
*
* @param string $name name
* @return string Camelized name
*/
protected function _camelize(string $name): string
{
return Inflector::camelize($name);
}
/**
* Creates the plural human name used in views
*
* @param string $name Controller name
* @return string Plural human name
*/
protected function _pluralHumanName(string $name): string
{
return Inflector::humanize(Inflector::underscore($name));
}
/**
* Find the correct path for a plugin. Scans $pluginPaths for the plugin you want.
*
* @param string $pluginName Name of the plugin you want ie. DebugKit
* @return string Path to the correct plugin.
*/
protected function _pluginPath(string $pluginName): string
{
if (Plugin::isLoaded($pluginName)) {
return Plugin::path($pluginName);
}
return current(App::path('plugins')) . $pluginName . DIRECTORY_SEPARATOR;
}
/**
* Return plugin's namespace
*
* @param string $pluginName Plugin name
* @return string Plugin's namespace
*/
protected function _pluginNamespace(string $pluginName): string
{
return str_replace('/', '\\', $pluginName);
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 5.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Event\EventManagerInterface;
interface EventAwareApplicationInterface
{
/**
* Register application events.
*
* @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on
* @return \Cake\Event\EventManagerInterface
*/
public function events(EventManagerInterface $eventManager): EventManagerInterface;
/**
* @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on
* @return \Cake\Event\EventManagerInterface
*/
public function pluginEvents(EventManagerInterface $eventManager): EventManagerInterface;
}
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Exception;
use RuntimeException;
use Throwable;
/**
* Base class that all CakePHP Exceptions extend.
*/
class CakeException extends RuntimeException
{
/**
* Array of attributes that are passed in from the constructor, and
* made available in the view when a development error is displayed.
*
* @var array
*/
protected array $_attributes = [];
/**
* Template string that has attributes sprintf()'ed into it.
*
* @var string
*/
protected string $_messageTemplate = '';
/**
* Default exception code
*
* @var int
*/
protected int $_defaultCode = 0;
/**
* Constructor.
*
* Allows you to create exceptions that are treated as framework errors and disabled
* when debug mode is off.
*
* @param array|string $message Either the string of the error message, or an array of attributes
* that are made available in the view, and sprintf()'d into Exception::$_messageTemplate
* @param int|null $code The error code
* @param \Throwable|null $previous the previous exception.
*/
public function __construct(array|string $message = '', ?int $code = null, ?Throwable $previous = null)
{
if (is_array($message)) {
$this->_attributes = $message;
$message = vsprintf($this->_messageTemplate, $message);
}
parent::__construct($message, $code ?? $this->_defaultCode, $previous);
}
/**
* Get the passed in attributes
*
* @return array
*/
public function getAttributes(): array
{
return $this->_attributes;
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 5.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Exception;
use Throwable;
interface HttpErrorCodeInterface extends Throwable
{
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Exception;
/**
* Exception raised when a plugin could not be found
*/
class MissingPluginException extends CakeException
{
/**
* @inheritDoc
*/
protected string $_messageTemplate = 'Plugin `%s` could not be found.';
}
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Http\MiddlewareQueue;
use Psr\Http\Server\RequestHandlerInterface;
/**
* An interface defining the methods that the
* http server depend on.
*/
interface HttpApplicationInterface extends RequestHandlerInterface
{
/**
* Load all the application configuration and bootstrap logic.
*
* Override this method to add additional bootstrap logic for your application.
*
* @return void
*/
public function bootstrap(): void;
/**
* Define the HTTP middleware layers for an application.
*
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to set in your App Class
* @return \Cake\Http\MiddlewareQueue
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue;
}
+326
View File
@@ -0,0 +1,326 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Core\Exception\CakeException;
use Cake\Utility\Hash;
use InvalidArgumentException;
/**
* A trait for reading and writing instance config
*
* Implementing objects are expected to declare a `$_defaultConfig` property.
*/
trait InstanceConfigTrait
{
/**
* Runtime config
*
* @var array<string, mixed>
*/
protected array $_config = [];
/**
* Whether the config property has already been configured with defaults
*
* @var bool
*/
protected bool $_configInitialized = false;
/**
* Sets the config.
*
* ### Usage
*
* Setting a specific value:
*
* ```
* $this->setConfig('key', $value);
* ```
*
* Setting a nested value:
*
* ```
* $this->setConfig('some.nested.key', $value);
* ```
*
* Updating multiple config settings at the same time:
*
* ```
* $this->setConfig(['one' => 'value', 'another' => 'value']);
* ```
*
* @param array<string, mixed>|string $key The key to set, or a complete array of configs.
* @param mixed|null $value The value to set.
* @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
* @return $this
* @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid.
*/
public function setConfig(array|string $key, mixed $value = null, bool $merge = true)
{
$this->initCfg();
$this->_configWrite($key, $value, $merge);
return $this;
}
/**
* Returns the config.
*
* ### Usage
*
* Reading the whole config:
*
* ```
* $this->getConfig();
* ```
*
* Reading a specific value:
*
* ```
* $this->getConfig('key');
* ```
*
* Reading a nested value:
*
* ```
* $this->getConfig('some.nested.key');
* ```
*
* Reading with default value:
*
* ```
* $this->getConfig('some-key', 'default-value');
* ```
*
* @param string|null $key The key to get or null for the whole config.
* @param mixed $default The return value when the key does not exist.
* @return ($key is null ? array : mixed) Configuration data at the named key or null if the key does not exist.
*/
public function getConfig(?string $key = null, mixed $default = null): mixed
{
$this->initCfg();
return $this->_configRead($key) ?? $default;
}
/**
* Returns the config for this specific key.
*
* The config value for this key must exist, it can never be null.
*
* @param string $key The key to get.
* @return mixed Configuration data at the named key
* @throws \InvalidArgumentException
*/
public function getConfigOrFail(string $key): mixed
{
$config = $this->getConfig($key);
if ($config === null) {
throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key));
}
return $config;
}
/**
* Merge provided config with existing config. Unlike `config()` which does
* a recursive merge for nested keys, this method does a simple merge.
*
* Setting a specific value:
*
* ```
* $this->configShallow('key', $value);
* ```
*
* Setting a nested value:
*
* ```
* $this->configShallow('some.nested.key', $value);
* ```
*
* Updating multiple config settings at the same time:
*
* ```
* $this->configShallow(['one' => 'value', 'another' => 'value']);
* ```
*
* @param array<string, mixed>|string $key The key to set, or a complete array of configs.
* @param mixed|null $value The value to set.
* @return $this
*/
public function configShallow(array|string $key, mixed $value = null)
{
$this->initCfg();
$this->_configWrite($key, $value, 'shallow');
return $this;
}
/**
* Deletes a config key.
*
* @param string $key Key to delete. It can be a dot separated string to delete nested keys.
* @return $this
*/
public function deleteConfig(string $key)
{
$this->initCfg();
$this->_configDelete($key);
return $this;
}
/**
* Initializes the config with the default config.
*
* @return void
*/
private function initCfg(): void
{
if (!$this->_configInitialized) {
$this->_config = $this->_defaultConfig;
$this->_configInitialized = true;
}
}
/**
* Reads a config key.
*
* @param string|null $key Key to read.
* @return ($key is null ? array : mixed)
*/
protected function _configRead(?string $key): mixed
{
if ($key === null) {
return $this->_config;
}
if (!str_contains($key, '.')) {
return $this->_config[$key] ?? null;
}
$return = $this->_config;
foreach (explode('.', $key) as $k) {
if (!is_array($return) || !isset($return[$k])) {
$return = null;
break;
}
$return = $return[$k];
}
return $return;
}
/**
* Writes a config key.
*
* @param array<string, mixed>|string $key Key to write to.
* @param mixed $value Value to write.
* @param string|bool $merge True to merge recursively, 'shallow' for simple merge,
* false to overwrite, defaults to false.
* @return void
* @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config
*/
protected function _configWrite(array|string $key, mixed $value, string|bool $merge = false): void
{
if (is_string($key) && $value === null) {
$this->_configDelete($key);
return;
}
if ($merge) {
$update = is_array($key) ? $key : [$key => $value];
if ($merge === 'shallow') {
$this->_config = array_merge($this->_config, Hash::expand($update));
} else {
$this->_config = Hash::merge($this->_config, Hash::expand($update));
}
return;
}
if (is_array($key)) {
foreach ($key as $k => $val) {
$this->_configWrite($k, $val);
}
return;
}
if (!str_contains($key, '.')) {
$this->_config[$key] = $value;
return;
}
$update = &$this->_config;
$stack = explode('.', $key);
foreach ($stack as $k) {
if (!is_array($update)) {
throw new CakeException(sprintf('Cannot set `%s` value.', $key));
}
$update[$k] ??= [];
$update = &$update[$k];
}
$update = $value;
}
/**
* Deletes a single config key.
*
* @param string $key Key to delete.
* @return void
* @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config
*/
protected function _configDelete(string $key): void
{
if (!str_contains($key, '.')) {
unset($this->_config[$key]);
return;
}
$update = &$this->_config;
$stack = explode('.', $key);
$length = count($stack);
foreach ($stack as $i => $k) {
if (!is_array($update)) {
throw new CakeException(sprintf('Cannot unset `%s` value.', $key));
}
if (!isset($update[$k])) {
break;
}
if ($i === $length - 1) {
unset($update[$k]);
break;
}
$update = &$update[$k];
}
}
}
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+406
View File
@@ -0,0 +1,406 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use ArrayIterator;
use Cake\Core\Exception\CakeException;
use Cake\Event\EventDispatcherInterface;
use Cake\Event\EventListenerInterface;
use Countable;
use IteratorAggregate;
use Traversable;
/**
* Acts as a registry/factory for objects.
*
* Provides registry & factory functionality for object types. Used
* as a super class for various composition based re-use features in CakePHP.
*
* Each subclass needs to implement the various abstract methods to complete
* the template method load().
*
* The ObjectRegistry is EventManager aware, but each extending class will need to use
* \Cake\Event\EventDispatcherTrait to attach and detach on set and bind
*
* @see \Cake\Controller\ComponentRegistry
* @see \Cake\View\HelperRegistry
* @see \Cake\Console\TaskRegistry
* @template TObject of object
* @template-implements \IteratorAggregate<string, TObject>
*/
abstract class ObjectRegistry implements Countable, IteratorAggregate
{
/**
* Map of loaded objects.
*
* @var array<string, TObject>
*/
protected array $_loaded = [];
/**
* Loads/constructs an object instance.
*
* Will return the instance in the registry if it already exists.
* If a subclass provides event support, you can use `$config['enabled'] = false`
* to exclude constructed objects from being registered for events.
*
* Using {@link \Cake\Controller\Component::$components} as an example. You can alias
* an object by setting the 'className' key, i.e.,
*
* ```
* protected $components = [
* 'Email' => [
* 'className' => 'App\Controller\Component\AliasedEmailComponent'
* ];
* ];
* ```
*
* All calls to the `Email` component would use `AliasedEmail` instead.
*
* @param string $name The name/class of the object to load.
* @param array<string, mixed> $config Additional settings to use when loading the object.
* @return TObject
* @throws \Exception If the class cannot be found.
*/
public function load(string $name, array $config = []): object
{
if (isset($config['className'])) {
if ($name === $config['className']) {
[, $objName] = pluginSplit($name);
} else {
$objName = $name;
}
$name = $config['className'];
} else {
[$plugin, $objName] = pluginSplit($name);
if ($plugin) {
$config['className'] = $name;
}
}
$loaded = isset($this->_loaded[$objName]);
if ($loaded && $config !== []) {
$this->_checkDuplicate($objName, $config);
}
if ($loaded) {
return $this->_loaded[$objName];
}
$className = $name;
if (is_string($name)) {
$className = $this->_resolveClassName($name);
if ($className === null) {
[$plugin, $name] = pluginSplit($name);
$this->_throwMissingClassError($name, $plugin);
}
}
$instance = $this->_create($className, $objName, $config);
$this->_loaded[$objName] = $instance;
return $instance;
}
/**
* Check for duplicate object loading.
*
* If a duplicate is being loaded and has different configuration, that is
* bad and an exception will be raised.
*
* An exception is raised, as replacing the object will not update any
* references other objects may have. Additionally, simply updating the runtime
* configuration is not a good option as we may be missing important constructor
* logic dependent on the configuration.
*
* @param string $name The name of the alias in the registry.
* @param array<string, mixed> $config The config data for the new instance.
* @return void
* @throws \Cake\Core\Exception\CakeException When a duplicate is found.
*/
protected function _checkDuplicate(string $name, array $config): void
{
$existing = $this->_loaded[$name];
$msg = sprintf('The `%s` alias has already been loaded.', $name);
$hasConfig = method_exists($existing, 'getConfig');
if (!$hasConfig) {
throw new CakeException($msg);
}
if (!$config) {
return;
}
$existingConfig = $existing->getConfig();
unset($config['enabled'], $existingConfig['enabled']);
$failure = null;
foreach ($config as $key => $value) {
if (!array_key_exists($key, $existingConfig)) {
$failure = " The `{$key}` was not defined in the previous configuration data.";
break;
}
if (isset($existingConfig[$key]) && $existingConfig[$key] !== $value) {
$failure = sprintf(
' The `%s` key has a value of `%s` but previously had a value of `%s`',
$key,
json_encode($value, JSON_THROW_ON_ERROR),
json_encode($existingConfig[$key], JSON_THROW_ON_ERROR),
);
break;
}
}
if ($failure) {
throw new CakeException($msg . $failure);
}
}
/**
* Should resolve the classname for a given object type.
*
* @param string $class The class to resolve.
* @return class-string<TObject>|null The resolved name or null for failure.
*/
abstract protected function _resolveClassName(string $class): ?string;
/**
* Throw an exception when the requested object name is missing.
*
* @param string $class The class that is missing.
* @param string|null $plugin The plugin $class is missing from.
* @return void
* @throws \Exception
*/
abstract protected function _throwMissingClassError(string $class, ?string $plugin): void;
/**
* Create an instance of a given classname.
*
* This method should construct and do any other initialization logic
* required.
*
* @param TObject|class-string<TObject> $class The class to build.
* @param string $alias The alias of the object.
* @param array<string, mixed> $config The Configuration settings for construction
* @return TObject
*/
abstract protected function _create(object|string $class, string $alias, array $config): object;
/**
* Get the list of loaded objects.
*
* @return array<string> List of object names.
*/
public function loaded(): array
{
return array_keys($this->_loaded);
}
/**
* Check whether a given object is loaded.
*
* @param string $name The object name to check for.
* @return bool True is object is loaded else false.
*/
public function has(string $name): bool
{
return isset($this->_loaded[$name]);
}
/**
* Get loaded object instance.
*
* @param string $name Name of object.
* @return TObject Object instance.
* @throws \Cake\Core\Exception\CakeException If not loaded or found.
*/
public function get(string $name): object
{
if (!isset($this->_loaded[$name])) {
throw new CakeException(sprintf('Unknown object `%s`.', $name));
}
return $this->_loaded[$name];
}
/**
* Provide public read access to the loaded objects
*
* @param string $name Name of property to read
* @return TObject|null
*/
public function __get(string $name): ?object
{
return $this->_loaded[$name] ?? null;
}
/**
* Provide isset access to _loaded
*
* @param string $name Name of object being checked.
* @return bool
*/
public function __isset(string $name): bool
{
return $this->has($name);
}
/**
* Sets an object.
*
* @param string $name Name of a property to set.
* @param TObject $object Object to set.
* @return void
*/
public function __set(string $name, object $object): void
{
$this->set($name, $object);
}
/**
* Unsets an object.
*
* @param string $name Name of a property to unset.
* @return void
*/
public function __unset(string $name): void
{
$this->unload($name);
}
/**
* Normalizes an object configuration array into associative form for making
* lazy loading easier.
*
* @param array $objects Array of child objects to normalize.
* @return array<string, array> Array of normalized objects.
*/
public function normalizeArray(array $objects): array
{
$normal = [];
foreach ($objects as $objectName => $config) {
if (is_int($objectName)) {
$objectName = $config;
$config = [];
}
[$plugin, $name] = pluginSplit($objectName);
if ($plugin) {
$config['className'] = $objectName;
}
$normal[$name] = $config;
}
return $normal;
}
/**
* Clear loaded instances in the registry.
*
* If the registry subclass has an event manager, the objects will be detached from events as well.
*
* @return $this
*/
public function reset()
{
foreach (array_keys($this->_loaded) as $name) {
$this->unload((string)$name);
}
return $this;
}
/**
* Set an object directly into the registry by name.
*
* If this collection implements events, the passed object will
* be attached into the event manager
*
* @param string $name The name of the object to set in the registry.
* @param TObject $object instance to store in the registry
* @return $this
*/
public function set(string $name, object $object)
{
// Just call unload if the object was loaded before
if (array_key_exists($name, $this->_loaded)) {
$this->unload($name);
}
if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) {
$this->getEventManager()->on($object);
}
$this->_loaded[$name] = $object;
return $this;
}
/**
* Remove an object from the registry.
*
* If this registry has an event manager, the object will be detached from any events as well.
*
* @param string $name The name of the object to remove from the registry.
* @return $this
*/
public function unload(string $name)
{
if (!isset($this->_loaded[$name])) {
throw new CakeException(sprintf('Object named `%s` is not loaded.', $name));
}
$object = $this->_loaded[$name];
if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) {
$this->getEventManager()->off($object);
}
unset($this->_loaded[$name]);
return $this;
}
/**
* Returns an array iterator.
*
* @return \Traversable<string, TObject>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->_loaded);
}
/**
* Returns the number of loaded objects.
*
* @return int
*/
public function count(): int
{
return count($this->_loaded);
}
/**
* Debug friendly object properties.
*
* @return array<string, mixed>
*/
public function __debugInfo(): array
{
$properties = get_object_vars($this);
if (isset($properties['_loaded'])) {
$properties['_loaded'] = array_keys($properties['_loaded']);
}
return $properties;
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
/**
* Plugin is used to load and locate plugins.
*
* It also can retrieve plugin paths and load their bootstrap and routes files.
*
* @link https://book.cakephp.org/5/en/plugins.html
*/
class Plugin
{
/**
* Holds a list of all loaded plugins and their configuration
*
* @var \Cake\Core\PluginCollection|null
*/
protected static ?PluginCollection $plugins = null;
/**
* Returns the filesystem path for a plugin
*
* @param string $name name of the plugin in CamelCase format
* @return string path to the plugin folder
* @throws \Cake\Core\Exception\MissingPluginException If the folder for plugin was not found
* or plugin has not been loaded.
*/
public static function path(string $name): string
{
$plugin = static::getCollection()->get($name);
return $plugin->getPath();
}
/**
* Returns the filesystem path for plugin's folder containing class files.
*
* @param string $name name of the plugin in CamelCase format.
* @return string Path to the plugin folder containing class files.
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
*/
public static function classPath(string $name): string
{
$plugin = static::getCollection()->get($name);
return $plugin->getClassPath();
}
/**
* Returns the filesystem path for plugin's folder containing config files.
*
* @param string $name name of the plugin in CamelCase format.
* @return string Path to the plugin folder containing config files.
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
*/
public static function configPath(string $name): string
{
$plugin = static::getCollection()->get($name);
return $plugin->getConfigPath();
}
/**
* Returns the filesystem path for plugin's folder containing template files.
*
* @param string $name name of the plugin in CamelCase format.
* @return string Path to the plugin folder containing template files.
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
*/
public static function templatePath(string $name): string
{
$plugin = static::getCollection()->get($name);
return $plugin->getTemplatePath();
}
/**
* Returns true if the plugin $plugin is already loaded.
*
* @param string $plugin Plugin name.
* @return bool
* @since 3.7.0
*/
public static function isLoaded(string $plugin): bool
{
return static::getCollection()->has($plugin);
}
/**
* Return a list of loaded plugins.
*
* @return array<string> A list of plugins that have been loaded
*/
public static function loaded(): array
{
$names = [];
foreach (static::getCollection() as $plugin) {
$names[] = $plugin->getName();
}
sort($names);
return $names;
}
/**
* Get the shared plugin collection.
*
* This method should generally not be used during application
* runtime as plugins should be set during Application startup.
*
* @return \Cake\Core\PluginCollection
*/
public static function getCollection(): PluginCollection
{
return static::$plugins ??= new PluginCollection();
}
/**
* Set the shared plugin collection.
*
* @param \Cake\Core\PluginCollection $collection
* @return void
*/
public static function setCollection(PluginCollection $collection): void
{
static::$plugins = $collection;
}
}
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Console\CommandCollection;
use Cake\Event\EventDispatcherInterface;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
/**
* Interface for Applications that leverage plugins & events.
*
* Events can be bound to the application event manager during
* the application's bootstrap and plugin bootstrap.
*
* @template TSubject
* @extends \Cake\Event\EventDispatcherInterface<\Cake\Http\BaseApplication>
*/
interface PluginApplicationInterface extends EventDispatcherInterface
{
/**
* Add a plugin to the loaded plugin set.
*
* If the named plugin does not exist, or does not define a Plugin class, an
* instance of `Cake\Core\BasePlugin` will be used. This generated class will have
* all plugin hooks enabled.
*
* @param \Cake\Core\PluginInterface|string $name The plugin name or plugin object.
* @param array<string, mixed> $config The configuration data for the plugin if using a string for $name
* @return $this
*/
public function addPlugin(PluginInterface|string $name, array $config = []);
/**
* Run bootstrap logic for loaded plugins.
*
* @return void
*/
public function pluginBootstrap(): void;
/**
* Run routes hooks for loaded plugins
*
* @param \Cake\Routing\RouteBuilder $routes The route builder to use.
* @return \Cake\Routing\RouteBuilder
*/
public function pluginRoutes(RouteBuilder $routes): RouteBuilder;
/**
* Run middleware hooks for plugins
*
* @param \Cake\Http\MiddlewareQueue $middleware The MiddlewareQueue to use.
* @return \Cake\Http\MiddlewareQueue
*/
public function pluginMiddleware(MiddlewareQueue $middleware): MiddlewareQueue;
/**
* Run console hooks for plugins
*
* @param \Cake\Console\CommandCollection $commands The CommandCollection to use.
* @return \Cake\Console\CommandCollection
*/
public function pluginConsole(CommandCollection $commands): CommandCollection;
}
+400
View File
@@ -0,0 +1,400 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Core\Exception\CakeException;
use Cake\Core\Exception\MissingPluginException;
use Cake\Utility\Hash;
use Countable;
use Generator;
use InvalidArgumentException;
use Iterator;
/**
* Plugin Collection
*
* Holds onto plugin objects loaded into an application, and
* provides methods for iterating, and finding plugins based
* on criteria.
*
* This class implements the Iterator interface to allow plugins
* to be iterated, handling the situation where a plugin's hook
* method (usually bootstrap) loads another plugin during iteration.
*
* While its implementation supported nested iteration it does not
* support using `continue` or `break` inside loops.
*
* @template-implements \Iterator<string, \Cake\Core\PluginInterface>
*/
class PluginCollection implements Iterator, Countable
{
/**
* Plugin list
*
* @var array<string, \Cake\Core\PluginInterface>
*/
protected array $plugins = [];
/**
* Names of plugins
*
* @var array<string>
*/
protected array $names = [];
/**
* Iterator position stack.
*
* @var array<int>
*/
protected array $positions = [];
/**
* Loop depth
*
* @var int
*/
protected int $loopDepth = -1;
/**
* Constructor
*
* @param array<\Cake\Core\PluginInterface> $plugins The map of plugins to add to the collection.
*/
public function __construct(array $plugins = [])
{
foreach ($plugins as $plugin) {
$this->add($plugin);
}
PluginConfig::loadInstallerConfig();
}
/**
* Add plugins from config array.
*
* @param array $config Configuration array. For e.g.:
* ```
* [
* 'Company/TestPluginThree',
* 'TestPlugin' => ['onlyDebug' => true, 'onlyCli' => true],
* 'Nope' => ['optional' => true],
* 'Named' => ['routes' => false, 'bootstrap' => false],
* ]
* ```
* @return void
*/
public function addFromConfig(array $config): void
{
$notDebug = !Configure::read('debug');
$notCli = PHP_SAPI !== 'cli';
/** @var array{onlyDebug?: bool, onlyCli?: bool, optional?: bool} $options */
foreach (Hash::normalize($config, default: []) as $name => $options) {
$onlyDebug = $options['onlyDebug'] ?? false;
$onlyCli = $options['onlyCli'] ?? false;
$optional = $options['optional'] ?? false;
if (
($onlyDebug && $notDebug)
|| ($onlyCli && $notCli)
) {
continue;
}
try {
$plugin = $this->create($name, $options);
$this->add($plugin);
} catch (MissingPluginException $e) {
if (!$optional) {
throw $e;
}
}
}
}
/**
* Locate a plugin path by looking at configuration data.
*
* This will use the `plugins` Configure key, and fallback to enumerating `App::path('plugins')`
*
* This method is not part of the official public API as plugins with
* no plugin class are being phased out.
*
* @param string $name The plugin name to locate a path for.
* @return string
* @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
* @internal
*/
public function findPath(string $name): string
{
// Ensure plugin config is loaded each time. This is necessary primarily
// for testing because the Configure::clear() call in TestCase::tearDown()
// wipes out all configuration including plugin paths config.
PluginConfig::loadInstallerConfig();
/** @var string|null $path */
$path = Configure::read('plugins.' . $name);
if ($path) {
return $path;
}
$pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $name);
$paths = App::path('plugins');
foreach ($paths as $path) {
if (is_dir($path . $pluginPath)) {
return $path . $pluginPath . DIRECTORY_SEPARATOR;
}
}
throw new MissingPluginException(['plugin' => $name]);
}
/**
* Add a plugin to the collection
*
* Plugins will be keyed by their names.
*
* @param \Cake\Core\PluginInterface $plugin The plugin to load.
* @return $this
*/
public function add(PluginInterface $plugin)
{
$name = $plugin->getName();
if (isset($this->plugins[$name])) {
throw new CakeException(sprintf('Plugin named `%s` is already loaded', $name));
}
$this->plugins[$name] = $plugin;
$this->names = array_keys($this->plugins);
return $this;
}
/**
* Remove a plugin from the collection if it exists.
*
* @param string $name The named plugin.
* @return $this
*/
public function remove(string $name)
{
unset($this->plugins[$name]);
$this->names = array_keys($this->plugins);
return $this;
}
/**
* Remove all plugins from the collection
*
* @return $this
*/
public function clear()
{
$this->plugins = [];
$this->names = [];
$this->positions = [];
$this->loopDepth = -1;
return $this;
}
/**
* Check whether the named plugin exists in the collection.
*
* @param string $name The named plugin.
* @return bool
*/
public function has(string $name): bool
{
return isset($this->plugins[$name]);
}
/**
* Get the a plugin by name.
*
* If a plugin isn't already loaded it will be autoloaded on first access
* and that plugins loaded this way may miss some hook methods.
*
* @param string $name The plugin to get.
* @return \Cake\Core\PluginInterface The plugin.
* @throws \Cake\Core\Exception\MissingPluginException when unknown plugins are fetched.
*/
public function get(string $name): PluginInterface
{
if ($this->has($name)) {
return $this->plugins[$name];
}
$plugin = $this->create($name);
$this->add($plugin);
return $plugin;
}
/**
* Create a plugin instance from a name/classname and configuration.
*
* @param string $name The plugin name or classname
* @param array<string, mixed> $config Configuration options for the plugin.
* @return \Cake\Core\PluginInterface
* @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created.
* @throws \InvalidArgumentException When class name cannot be found or an empty name is provided.
* @phpstan-param class-string<\Cake\Core\PluginInterface>|string $name
*/
public function create(string $name, array $config = []): PluginInterface
{
if ($name === '') {
throw new InvalidArgumentException('Plugin name cannot be empty.');
}
if (str_contains($name, '\\')) {
if (!class_exists($name)) {
throw new InvalidArgumentException(sprintf('Class `%s` does not exist.', $name));
}
/** @var \Cake\Core\PluginInterface */
return new $name($config);
}
$config += ['name' => $name];
$namespace = str_replace('/', '\\', $name);
$pos = strpos($name, '/');
$namePart = $pos === false ? $name : substr($name, $pos + 1);
// Check for [Vendor/]Foo/FooPlugin class
$className = $namespace . '\\' . $namePart . 'Plugin';
if (!class_exists($className)) {
// Check for [Vendor/]Foo/Plugin class
$className = $namespace . '\\' . 'Plugin';
if (class_exists($className)) {
deprecationWarning(
'5.3.0',
'Loading plugins with a plugin class named `Plugin` is deprecated.'
. " Rename the class to `{$namePart}Plugin` instead.",
);
} else {
$className = BasePlugin::class;
if (empty($config['path'])) {
$config['path'] = $this->findPath($name);
}
deprecationWarning(
'5.3.0',
'Loading plugins without a plugin class is deprecated.'
. " You can create the missing class using `bin/cake bake plugin {$name} --class-only`.",
);
}
}
/** @var class-string<\Cake\Core\PluginInterface> $className */
return new $className($config);
}
/**
* Implementation of Countable.
*
* Get the number of plugins in the collection.
*
* @return int
*/
public function count(): int
{
return count($this->plugins);
}
/**
* Part of Iterator Interface
*
* @return void
*/
public function next(): void
{
$this->positions[$this->loopDepth]++;
}
/**
* Part of Iterator Interface
*
* @return string
*/
public function key(): string
{
return $this->names[$this->positions[$this->loopDepth]];
}
/**
* Part of Iterator Interface
*
* @return \Cake\Core\PluginInterface
*/
public function current(): PluginInterface
{
$position = $this->positions[$this->loopDepth];
$name = $this->names[$position];
return $this->plugins[$name];
}
/**
* Part of Iterator Interface
*
* @return void
*/
public function rewind(): void
{
$this->positions[] = 0;
$this->loopDepth += 1;
}
/**
* Part of Iterator Interface
*
* @return bool
*/
public function valid(): bool
{
$valid = isset($this->names[$this->positions[$this->loopDepth]]);
if (!$valid) {
array_pop($this->positions);
$this->loopDepth -= 1;
}
return $valid;
}
/**
* Filter the plugins to those with the named hook enabled.
*
* @param string $hook The hook to filter plugins by
* @return \Generator<\Cake\Core\PluginInterface> A generator containing matching plugins.
* @throws \InvalidArgumentException on invalid hooks
*/
public function with(string $hook): Generator
{
if (!in_array($hook, PluginInterface::VALID_HOOKS, true)) {
throw new InvalidArgumentException(sprintf('The `%s` hook is not a known plugin hook.', $hook));
}
foreach ($this as $plugin) {
if ($plugin->isEnabled($hook)) {
yield $plugin;
}
}
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 5.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Core\Exception\CakeException;
use Cake\Utility\Hash;
/**
* PluginConfig contains all available plugins and their config if/how they should be loaded
*
* @internal
*/
class PluginConfig
{
/**
* Load the path information stored in vendor/cakephp-plugins.php
*
* This file is generated by the cakephp/plugin-installer package and used
* to locate plugins on the filesystem as applications can use `extra.plugin-paths`
* in their composer.json file to move plugin outside of vendor/
*
* @internal
* @return void
*/
public static function loadInstallerConfig(): void
{
if (Configure::check('plugins')) {
return;
}
$vendorFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
if (!is_file($vendorFile)) {
$vendorFile = dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
if (!is_file($vendorFile)) {
Configure::write(['plugins' => []]);
return;
}
}
$config = require $vendorFile;
Configure::write($config);
}
/**
* Get the config how plugins should be loaded
*
* @param string|null $path The absolute path to the composer.lock file to retrieve the versions from
* @return array
*/
public static function getAppConfig(?string $path = null): array
{
self::loadInstallerConfig();
// phpcs:ignore
$pluginLoadConfig = @include CONFIG . 'plugins.php';
if (is_array($pluginLoadConfig)) {
$pluginLoadConfig = Hash::normalize($pluginLoadConfig);
} else {
$pluginLoadConfig = [];
}
try {
$composerVersions = self::getVersions($path);
} catch (CakeException) {
$composerVersions = [];
}
$result = [];
$availablePlugins = Configure::read('plugins', []);
if ($availablePlugins && is_array($availablePlugins)) {
foreach ($availablePlugins as $pluginName => $pluginPath) {
if ($pluginLoadConfig && array_key_exists($pluginName, $pluginLoadConfig)) {
$options = $pluginLoadConfig[$pluginName];
$hooks = PluginInterface::VALID_HOOKS;
$mainConfig = [
'isLoaded' => true,
'onlyDebug' => $options['onlyDebug'] ?? false,
'onlyCli' => $options['onlyCli'] ?? false,
'optional' => $options['optional'] ?? false,
];
foreach ($hooks as $hook) {
$mainConfig[$hook] = $options[$hook] ?? true;
}
$result[$pluginName] = $mainConfig;
} else {
$result[$pluginName]['isLoaded'] = false;
}
try {
$packageName = self::getPackageNameFromPath($pluginPath);
$result[$pluginName]['packagePath'] = $pluginPath;
$result[$pluginName]['package'] = $packageName;
} catch (CakeException) {
$packageName = null;
}
if ($composerVersions && $packageName) {
if (array_key_exists($packageName, $composerVersions['packages'])) {
$result[$pluginName]['version'] = $composerVersions['packages'][$packageName];
$result[$pluginName]['isDevPackage'] = false;
} elseif (array_key_exists($packageName, $composerVersions['devPackages'])) {
$result[$pluginName]['version'] = $composerVersions['devPackages'][$packageName];
$result[$pluginName]['isDevPackage'] = true;
}
}
}
}
$diff = array_diff(array_keys($pluginLoadConfig), array_keys($availablePlugins));
foreach ($diff as $unknownPlugin) {
$result[$unknownPlugin]['isLoaded'] = false;
$result[$unknownPlugin]['isUnknown'] = true;
}
return $result;
}
/**
* @param string|null $path The absolute path to the composer.lock file to retrieve the versions from
* @return array
*/
public static function getVersions(?string $path = null): array
{
$lockFilePath = $path ?? ROOT . DIRECTORY_SEPARATOR . 'composer.lock';
if (!file_exists($lockFilePath)) {
throw new CakeException(sprintf('composer.lock does not exist in %s', $lockFilePath));
}
$lockFile = file_get_contents($lockFilePath);
if ($lockFile === false) {
throw new CakeException(sprintf('Could not read composer.lock: %s', $lockFilePath));
}
$lockFileJson = json_decode($lockFile, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new CakeException(sprintf(
'Error parsing composer.lock: %s',
json_last_error_msg(),
));
}
$packages = Hash::combine($lockFileJson['packages'], '{n}.name', '{n}.version');
$devPackages = Hash::combine($lockFileJson['packages-dev'], '{n}.name', '{n}.version');
return [
'packages' => $packages,
'devPackages' => $devPackages,
];
}
/**
* @param string $path
* @return string
*/
protected static function getPackageNameFromPath(string $path): string
{
$jsonPath = $path . DS . 'composer.json';
if (!file_exists($jsonPath)) {
throw new CakeException(sprintf('composer.json does not exist in %s', $jsonPath));
}
$jsonString = file_get_contents($jsonPath);
if ($jsonString === false) {
throw new CakeException(sprintf('Could not read composer.json: %s', $jsonPath));
}
$json = json_decode($jsonString, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new CakeException(sprintf(
'Error parsing %ss: %s',
$jsonPath,
json_last_error_msg(),
));
}
return $json['name'];
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use Cake\Console\CommandCollection;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
/**
* Plugin Interface
*
* @method \Cake\Event\EventManagerInterface events(\Cake\Event\EventManagerInterface $eventManager)
*/
interface PluginInterface
{
/**
* List of valid hooks.
*
* @var array<string>
*/
public const VALID_HOOKS = ['bootstrap', 'console', 'middleware', 'routes', 'services', 'events'];
/**
* Get the name of this plugin.
*
* @return string
*/
public function getName(): string;
/**
* Get the filesystem path to this plugin
*
* @return string
*/
public function getPath(): string;
/**
* Get the filesystem path to configuration for this plugin
*
* @return string
*/
public function getConfigPath(): string;
/**
* Get the filesystem path to configuration for this plugin
*
* @return string
*/
public function getClassPath(): string;
/**
* Get the filesystem path to templates for this plugin
*
* @return string
*/
public function getTemplatePath(): string;
/**
* Load all the application configuration and bootstrap logic.
*
* The default implementation of this method will include the `config/bootstrap.php` in the plugin if it exist. You
* can override this method to replace that behavior.
*
* The host application is provided as an argument. This allows you to load additional
* plugin dependencies, or attach events.
*
* @param \Cake\Core\PluginApplicationInterface $app The host application
* @return void
*/
public function bootstrap(PluginApplicationInterface $app): void;
/**
* Add console commands for the plugin.
*
* @param \Cake\Console\CommandCollection $commands The command collection to update
* @return \Cake\Console\CommandCollection
*/
public function console(CommandCollection $commands): CommandCollection;
/**
* Add middleware for the plugin.
*
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update.
* @return \Cake\Http\MiddlewareQueue
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue;
/**
* Add routes for the plugin.
*
* The default implementation of this method will include the `config/routes.php` in the plugin if it exists. You
* can override this method to replace that behavior.
*
* @param \Cake\Routing\RouteBuilder $routes The route builder to update.
* @return void
*/
public function routes(RouteBuilder $routes): void;
/**
* Register plugin services to the application's container
*
* @param \Cake\Core\ContainerInterface $container Container instance.
* @return void
*/
public function services(ContainerInterface $container): void;
/**
* Disables the named hook
*
* @param string $hook The hook to disable
* @return $this
*/
public function disable(string $hook);
/**
* Enables the named hook
*
* @param string $hook The hook to disable
* @return $this
*/
public function enable(string $hook);
/**
* Check if the named hook is enabled
*
* @param string $hook The hook to check
* @return bool
*/
public function isEnabled(string $hook): bool;
}
+37
View File
@@ -0,0 +1,37 @@
[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/core.svg?style=flat-square)](https://packagist.org/packages/cakephp/core)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt)
# CakePHP Core Classes
A set of classes used for configuration files reading and storing.
This repository contains the classes that are used as glue for creating the CakePHP framework.
## Usage
You can use the `Configure` class to store arbitrary configuration data:
```php
use Cake\Core\Configure;
use Cake\Core\Configure\Engine\PhpConfig;
Configure::write('Company.name','Pizza, Inc.');
Configure::read('Company.name'); // Returns: 'Pizza, Inc.'
```
It also possible to load configuration from external files:
```php
Configure::config('default', new PhpConfig('/path/to/config/folder'));
Configure::load('app', 'default', false);
Configure::load('other_config', 'default');
```
And write the configuration back into files:
```php
Configure::dump('my_config', 'default');
```
## Documentation
Please make sure you check the [official documentation](https://book.cakephp.org/5/en/development/configuration.html)
+95
View File
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Retry;
use Closure;
use Exception;
/**
* Allows any action to be retried in case of an exception.
*
* This class can be parametrized with a strategy, which will be followed
* to determine whether the action should be retried.
*/
class CommandRetry
{
/**
* The strategy to follow should the executed action fail.
*
* @var \Cake\Core\Retry\RetryStrategyInterface
*/
protected RetryStrategyInterface $strategy;
/**
* @var int
*/
protected int $maxRetries;
/**
* @var int
*/
protected int $numRetries;
/**
* Creates the CommandRetry object with the given strategy and retry count
*
* @param \Cake\Core\Retry\RetryStrategyInterface $strategy The strategy to follow should the action fail
* @param int $maxRetries The maximum number of retry attempts allowed
*/
public function __construct(RetryStrategyInterface $strategy, int $maxRetries = 1)
{
$this->strategy = $strategy;
$this->maxRetries = $maxRetries;
}
/**
* The number of retries to perform in case of failure
*
* @param \Closure $action Callback to run for each attempt
* @return mixed The return value of the passed action callable
* @throws \Exception Throws exception from last failure
*/
public function run(Closure $action): mixed
{
$this->numRetries = 0;
while (true) {
try {
return $action();
} catch (Exception $e) {
if (
$this->numRetries < $this->maxRetries &&
$this->strategy->shouldRetry($e, $this->numRetries)
) {
$this->numRetries++;
continue;
}
throw $e;
}
}
}
/**
* Returns the last number of retry attempts.
*
* @return int
*/
public function getRetries(): int
{
return $this->numRetries;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\Retry;
use Exception;
/**
* Used to instruct a CommandRetry object on whether a retry
* for an action should be performed
*/
interface RetryStrategyInterface
{
/**
* Returns true if the action can be retried, false otherwise.
*
* @param \Exception $exception The exception that caused the action to fail
* @param int $retryCount The number of times action has been retried
* @return bool Whether it is OK to retry the action
*/
public function shouldRetry(Exception $exception, int $retryCount): bool;
}
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
/**
* Read-only wrapper for configuration data
*
* Intended for use with {@link \Cake\Core\Container} as
* a typehintable way for services to have application
* configuration injected as arrays cannot be typehinted.
*/
class ServiceConfig
{
/**
* Read a configuration key
*
* @param string $path The path to read.
* @param mixed $default The default value to use if $path does not exist.
* @return mixed The configuration data or $default value.
*/
public function get(string $path, mixed $default = null): mixed
{
return Configure::read($path, $default);
}
/**
* Check if $path exists and has a non-null value.
*
* @param string $path The path to check.
* @return bool True if the configuration data exists.
*/
public function has(string $path): bool
{
return Configure::check($path);
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use League\Container\DefinitionContainerInterface;
use League\Container\ServiceProvider\AbstractServiceProvider;
use League\Container\ServiceProvider\BootableServiceProviderInterface;
use LogicException;
/**
* Container ServiceProvider
*
* Service provider bundle related services together helping
* to organize your application's dependencies. They also help
* improve performance of applications with many services by
* allowing service registration to be deferred until services are needed.
*/
abstract class ServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
{
/**
* List of ids of services this provider provides.
*
* @var array<string>
* @see ServiceProvider::provides()
*/
protected array $provides = [];
/**
* Get the container.
*
* @return \Cake\Core\ContainerInterface
*/
public function getContainer(): DefinitionContainerInterface
{
$container = parent::getContainer();
assert(
$container instanceof ContainerInterface,
sprintf(
'Unexpected container type. Expected `%s` got `%s` instead.',
ContainerInterface::class,
get_debug_type($container),
),
);
return $container;
}
/**
* Delegate to the bootstrap() method
*
* This method wraps the league/container function so users
* only need to use the CakePHP bootstrap() interface.
*
* @return void
*/
public function boot(): void
{
$this->bootstrap($this->getContainer());
}
/**
* Bootstrap hook for ServiceProviders
*
* This hook should be implemented if your service provider
* needs to register additional service providers, load configuration
* files or do any other work when the service provider is added to the
* container.
*
* @param \Cake\Core\ContainerInterface $container The container to add services to.
* @return void
*/
public function bootstrap(ContainerInterface $container): void
{
}
/**
* Call the abstract services() method.
*
* This method primarily exists as a shim between the interface
* that league/container has and the one we want to offer in CakePHP.
*
* @return void
*/
public function register(): void
{
$this->services($this->getContainer());
}
/**
* The provides method is a way to let the container know that a service
* is provided by this service provider.
*
* Every service registered via this service provider must have an
* alias added to this array or it will be ignored.
*
* @param string $id Identifier.
* @return bool
*/
public function provides(string $id): bool
{
if (!$this->provides) {
throw new LogicException(
'The property `$provides` should contain a list with service ids for this service provider',
);
}
return in_array($id, $this->provides, true);
}
/**
* Register the services in a provider.
*
* All services registered in this method should also be included in the $provides
* property so that services can be located.
*
* @param \Cake\Core\ContainerInterface $container The container to add services to.
* @return void
*/
abstract public function services(ContainerInterface $container): void;
}
+333
View File
@@ -0,0 +1,333 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use BadMethodCallException;
use InvalidArgumentException;
use LogicException;
/**
* A trait that provides a set of static methods to manage configuration
* for classes that provide an adapter facade or need to have sets of
* configuration data registered and manipulated.
*
* Implementing objects are expected to declare a static `$_dsnClassMap` property.
*/
trait StaticConfigTrait
{
/**
* Configuration sets.
*
* @var array<string|int, array<string, mixed>>
*/
protected static array $_config = [];
/**
* This method can be used to define configuration adapters for an application.
*
* To change an adapter's configuration at runtime, first drop the adapter and then
* reconfigure it.
*
* Adapters will not be constructed until the first operation is done.
*
* ### Usage
*
* Assuming that the class' name is `Cache` the following scenarios
* are supported:
*
* Setting a cache engine up.
*
* ```
* Cache::setConfig('default', $settings);
* ```
*
* Injecting a constructed adapter in:
*
* ```
* Cache::setConfig('default', $instance);
* ```
*
* Configure multiple adapters at once:
*
* ```
* Cache::setConfig($arrayOfConfig);
* ```
*
* @param array<string, mixed>|string $key The name of the configuration, or an array of multiple configs.
* @param mixed $config Configuration value. Generally an array of name => configuration data for adapter.
* @throws \BadMethodCallException When trying to modify an existing config.
* @throws \LogicException When trying to store an invalid structured config array.
* @return void
*/
public static function setConfig(array|string $key, mixed $config = null): void
{
if ($config === null) {
if (!is_array($key)) {
throw new LogicException('If config is null, key must be an array.');
}
foreach ($key as $name => $settings) {
static::setConfig((string)$name, $settings);
}
return;
}
if (!is_string($key)) {
throw new LogicException('If config is not null, key must be a string.');
}
if (isset(static::$_config[$key])) {
throw new BadMethodCallException(sprintf('Cannot reconfigure existing key `%s`.', $key));
}
if (is_object($config)) {
$config = ['className' => $config];
}
if (is_array($config) && isset($config['url'])) {
$parsed = static::parseDsn($config['url']);
unset($config['url']);
$config = $parsed + $config;
}
if (isset($config['engine']) && empty($config['className'])) {
$config['className'] = $config['engine'];
unset($config['engine']);
}
static::$_config[$key] = $config;
}
/**
* Reads existing configuration.
*
* @param string $key The name of the configuration.
* @return mixed|null Configuration data at the named key or null if the key does not exist.
*/
public static function getConfig(string $key): mixed
{
return static::$_config[$key] ?? null;
}
/**
* Reads existing configuration for a specific key.
*
* The config value for this key must exist, it can never be null.
*
* @param string $key The name of the configuration.
* @return mixed Configuration data at the named key.
* @throws \InvalidArgumentException If value does not exist.
*/
public static function getConfigOrFail(string $key): mixed
{
if (!isset(static::$_config[$key])) {
throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key));
}
return static::$_config[$key];
}
/**
* Drops a constructed adapter.
*
* If you wish to modify an existing configuration, you should drop it,
* change configuration and then re-add it.
*
* If the implementing objects supports a `$_registry` object the named configuration
* will also be unloaded from the registry.
*
* @param string $config An existing configuration you wish to remove.
* @return bool Success of the removal, returns false when the config does not exist.
*/
public static function drop(string $config): bool
{
if (!isset(static::$_config[$config])) {
return false;
}
/** @phpstan-ignore-next-line */
if (isset(static::$_registry)) {
static::$_registry->unload($config);
}
unset(static::$_config[$config]);
return true;
}
/**
* Returns an array containing the named configurations
*
* @return array<string> Array of configurations.
*/
public static function configured(): array
{
$configurations = array_keys(static::$_config);
return array_map(function (int|string $key) {
return (string)$key;
}, $configurations);
}
/**
* Parses a DSN into a valid connection configuration
*
* This method allows setting a DSN using formatting similar to that used by PEAR::DB.
* The following is an example of its usage:
*
* ```
* $dsn = 'mysql://user:pass@localhost/database?';
* $config = ConnectionManager::parseDsn($dsn);
*
* $dsn = 'Cake\Log\Engine\FileLog://?types=notice,info,debug&file=debug&path=LOGS';
* $config = Log::parseDsn($dsn);
*
* $dsn = 'smtp://user:secret@localhost:25?timeout=30&client=null&tls=null';
* $config = Email::parseDsn($dsn);
*
* $dsn = 'file:///?className=\My\Cache\Engine\FileEngine';
* $config = Cache::parseDsn($dsn);
*
* $dsn = 'File://?prefix=myapp_cake_translations_&serialize=true&duration=+2 minutes&path=/tmp/persistent/';
* $config = Cache::parseDsn($dsn);
* ```
*
* For all classes, the value of `scheme` is set as the value of both the `className`
* unless they have been otherwise specified.
*
* Note that querystring arguments are also parsed and set as values in the returned configuration.
*
* @param string $dsn The DSN string to convert to a configuration array
* @return array<int|string, array|bool|string|null> The configuration array to be stored after parsing the DSN
* @throws \InvalidArgumentException If not passed a string, or passed an invalid string
*/
public static function parseDsn(string $dsn): array
{
if (!$dsn) {
return [];
}
$pattern = <<<'REGEXP'
{
^
(?P<_scheme>
(?P<scheme>[\w\\\\]+)://
)
(?P<_username>
(?P<username>.*?)
(?P<_password>
:(?P<password>.*?)
)?
@
)?
(?P<_host>
(?P<host>\[[^]]+]|[^?#/:@]+)
(?P<_port>
:(?P<port>\d+)
)?
)?
(?P<_path>
(?P<path>/[^?#]*)
)?
(?P<_query>
\?(?P<query>[^#]*)
)?
(?P<_fragment>
\#(?P<fragment>.*)
)?
$
}x
REGEXP;
preg_match($pattern, $dsn, $parsed);
if (!$parsed) {
throw new InvalidArgumentException(sprintf('The DSN string `%s` could not be parsed.', $dsn));
}
$exists = [];
/**
* @var string|int $k
*/
foreach ($parsed as $k => $v) {
if (is_int($k)) {
unset($parsed[$k]);
} elseif (str_starts_with($k, '_')) {
$exists[substr($k, 1)] = ($v !== '');
unset($parsed[$k]);
} elseif ($v === '' && !$exists[$k]) {
unset($parsed[$k]);
}
}
$query = '';
if (isset($parsed['query'])) {
$query = $parsed['query'];
unset($parsed['query']);
}
parse_str($query, $queryArgs);
/**
* @var string $key
*/
foreach ($queryArgs as $key => $value) {
if ($value === 'true') {
$queryArgs[$key] = true;
} elseif ($value === 'false') {
$queryArgs[$key] = false;
} elseif ($value === 'null') {
$queryArgs[$key] = null;
}
}
$parsed = $queryArgs + $parsed;
if (empty($parsed['className'])) {
$classMap = static::getDsnClassMap();
/** @var string $scheme */
$scheme = $parsed['scheme'];
$parsed['className'] = $scheme;
if (isset($classMap[$scheme])) {
$parsed['className'] = $classMap[$scheme];
}
}
return $parsed;
}
/**
* Updates the DSN class map for this class.
*
* @param array<string, string> $map Additions/edits to the class map to apply.
* @return void
* @phpstan-param array<string, class-string> $map
*/
public static function setDsnClassMap(array $map): void
{
static::$_dsnClassMap = $map + static::$_dsnClassMap;
}
/**
* Returns the DSN class map for this class.
*
* @return array<string, class-string>
*/
public static function getDsnClassMap(): array
{
return static::$_dsnClassMap;
}
}
@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 4.2.0
* @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core\TestSuite;
use Cake\Core\Configure;
use Cake\Core\ConsoleApplicationInterface;
use Cake\Core\ContainerInterface;
use Cake\Core\HttpApplicationInterface;
use Cake\Event\EventInterface;
use Cake\Routing\Router;
use Closure;
use League\Container\Exception\NotFoundException;
use LogicException;
use PHPUnit\Framework\Attributes\After;
/**
* A set of methods used for defining container services
* in test cases.
*
* This trait leverages the `Application.buildContainer` event
* to inject the mocked services into the container that the
* application uses.
*/
trait ContainerStubTrait
{
/**
* The customized application class name.
*
* @phpstan-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null
* @var string|null
*/
protected ?string $_appClass = null;
/**
* The customized application constructor arguments.
*
* @var array|null
*/
protected ?array $_appArgs = null;
/**
* The collection of container services.
*
* @var array<string, mixed>
*/
private array $containerServices = [];
/**
* Configure the application class to use in integration tests.
*
* @param string $class The application class name.
* @param array|null $constructorArgs The constructor arguments for your application class.
* @return void
* @phpstan-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class
*/
public function configApplication(string $class, ?array $constructorArgs): void
{
$this->_appClass = $class;
$this->_appArgs = $constructorArgs;
}
/**
* Create an application instance.
*
* Uses the configuration set in `configApplication()`.
*
* @return \Cake\Core\HttpApplicationInterface|\Cake\Core\ConsoleApplicationInterface
*/
protected function createApp(): HttpApplicationInterface|ConsoleApplicationInterface
{
if (class_exists(Router::class)) {
Router::resetRoutes();
}
if ($this->_appClass) {
$appClass = $this->_appClass;
} else {
/** @var class-string<\Cake\Http\BaseApplication> $appClass */
$appClass = Configure::read('App.namespace') . '\Application';
}
if (!class_exists($appClass)) {
throw new LogicException(sprintf('Cannot load `%s` for use in integration testing.', $appClass));
}
$appArgs = $this->_appArgs ?: [CONFIG];
$app = new $appClass(...$appArgs);
if ($this->containerServices && method_exists($app, 'getEventManager')) {
$app->getEventManager()->on('Application.buildContainer', [$this, 'modifyContainer']);
}
foreach ($this->appPluginsToLoad as $pluginName => $config) {
if (is_array($config)) {
$app->addPlugin($pluginName, $config);
} else {
$app->addPlugin($config);
}
}
return $app;
}
/**
* Add a mocked service to the container.
*
* When the container is created the provided classname
* will be mapped to the factory function. The factory
* function will be used to create mocked services.
*
* @param string $class The class or interface you want to define.
* @param \Closure $factory The factory function for mocked services.
* @return $this
*/
public function mockService(string $class, Closure $factory)
{
$this->containerServices[$class] = $factory;
return $this;
}
/**
* Remove a mocked service to the container.
*
* @param string $class The class or interface you want to remove.
* @return $this
*/
public function removeMockService(string $class)
{
unset($this->containerServices[$class]);
return $this;
}
/**
* Wrap the application's container with one containing mocks.
*
* If any mocked services are defined, the application's container
* will be replaced with one containing mocks. The original
* container will be set as a delegate to the mock container.
*
* @param \Cake\Event\EventInterface $event The event
* @param \Cake\Core\ContainerInterface $container The container to wrap.
* @return void
*/
public function modifyContainer(EventInterface $event, ContainerInterface $container): void
{
if (!$this->containerServices) {
return;
}
foreach ($this->containerServices as $key => $factory) {
if ($container->has($key)) {
try {
$container->extend($key)->setConcrete($factory);
} catch (NotFoundException) {
$container->add($key, $factory);
}
} else {
$container->add($key, $factory);
}
}
$event->setResult($container);
}
/**
* Clears any mocks that were defined and cleans
* up application class configuration.
*
* @return void
*/
#[After]
public function cleanupContainer(): void
{
$this->_appArgs = null;
$this->_appClass = null;
$this->containerServices = [];
}
}
// phpcs:disable
class_alias(
'Cake\Core\TestSuite\ContainerStubTrait',
'Cake\TestSuite\ContainerStubTrait'
);
// phpcs:enable
+53
View File
@@ -0,0 +1,53 @@
{
"name": "cakephp/core",
"description": "CakePHP Framework Core classes",
"type": "library",
"keywords": [
"cakephp",
"framework",
"core"
],
"homepage": "https://cakephp.org",
"license": "MIT",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/core/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/cakephp/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/core"
},
"require": {
"php": ">=8.2",
"cakephp/utility": "^5.3.0",
"league/container": "^5.1",
"psr/container": "^1.1 || ^2.0"
},
"autoload": {
"psr-4": {
"Cake\\Core\\": "."
},
"files": [
"functions.php"
]
},
"provide": {
"psr/container-implementation": "^2.0"
},
"suggest": {
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
"cakephp/cache": "To use Configure::store() and restore().",
"league/container": "To use Container and ServiceProvider classes"
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"branch-alias": {
"dev-5.next": "5.3.x-dev"
}
}
}
+539
View File
@@ -0,0 +1,539 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Core;
use JsonException;
use Stringable;
if (!defined('DS')) {
/**
* Defines DS as short form of DIRECTORY_SEPARATOR.
*/
define('DS', DIRECTORY_SEPARATOR);
}
if (!defined('CAKE_DATE_RFC7231')) {
define('CAKE_DATE_RFC7231', 'D, d M Y H:i:s \G\M\T');
}
if (!function_exists('Cake\Core\pathCombine')) {
/**
* Combines parts with a forward-slash `/`.
*
* Skips adding a forward-slash if either `/` or `\` already exists.
*
* @param array<string> $parts
* @param bool|null $trailing Determines how trailing slashes are handled
* - If true, ensures a trailing forward-slash is added if one doesn't exist
* - If false, ensures any trailing slash is removed
* - if null, ignores trailing slashes
* @return string
*/
function pathCombine(array $parts, ?bool $trailing = null): string
{
$numParts = count($parts);
if ($numParts === 0) {
if ($trailing === true) {
return '/';
}
return '';
}
$path = $parts[0];
for ($i = 1; $i < $numParts; ++$i) {
$part = $parts[$i];
if ($part === '') {
continue;
}
if ($path[-1] === '/' || $path[-1] === '\\') {
if ($part[0] === '/' || $part[0] === '\\') {
$path .= substr($part, 1);
} else {
$path .= $part;
}
} elseif ($part[0] === '/' || $part[0] === '\\') {
$path .= $part;
} else {
$path .= '/' . $part;
}
}
if ($trailing === true) {
if ($path === '' || ($path[-1] !== '/' && $path[-1] !== '\\')) {
$path .= '/';
}
} elseif ($trailing === false) {
if ($path !== '' && ($path[-1] === '/' || $path[-1] === '\\')) {
$path = substr($path, 0, -1);
}
}
return $path;
}
}
if (!function_exists('Cake\Core\h')) {
/**
* Convenience method for htmlspecialchars.
*
* @param mixed $text Text to wrap through htmlspecialchars. Also works with arrays, and objects.
* Arrays will be mapped and have all their elements escaped. Objects will be string cast if they
* implement a `__toString` method. Otherwise, the class name will be used.
* Other scalar types will be returned unchanged.
* @param bool $double Encode existing html entities.
* @param string|null $charset Character set to use when escaping.
* Defaults to config value in `mb_internal_encoding()` or 'UTF-8'.
* @return mixed Wrapped text.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#h
*/
function h(mixed $text, bool $double = true, ?string $charset = null): mixed
{
if (is_string($text)) {
//optimize for strings
} elseif (is_array($text)) {
$texts = [];
foreach ($text as $k => $t) {
$texts[$k] = h($t, $double, $charset);
}
return $texts;
} elseif (is_object($text)) {
if ($text instanceof Stringable) {
$text = (string)$text;
} else {
$text = '(object)' . $text::class;
}
} elseif ($text === null || is_scalar($text)) {
return $text;
}
static $defaultCharset = false;
if ($defaultCharset === false) {
$defaultCharset = mb_internal_encoding() ?: 'UTF-8';
}
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset ?: $defaultCharset, $double);
}
}
if (!function_exists('Cake\Core\pluginSplit')) {
/**
* Splits a dot syntax plugin name into its plugin and class name.
* If $name does not have a dot, then index 0 will be null.
*
* Commonly used like
* ```
* list($plugin, $name) = pluginSplit($name);
* ```
*
* @param string $name The name you want to plugin split.
* @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it.
* @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null.
* @return array Array with 2 indexes. 0 => plugin name, 1 => class name.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit
* @phpstan-return array{string|null, string}
*/
function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array
{
if (str_contains($name, '.')) {
$parts = explode('.', $name, 2);
if ($dotAppend) {
$parts[0] .= '.';
}
/** @phpstan-var array{string, string} */
return $parts;
}
return [$plugin, $name];
}
}
if (!function_exists('Cake\Core\namespaceSplit')) {
/**
* Split the namespace from the classname.
*
* Commonly used like `list($namespace, $className) = namespaceSplit($class);`.
*
* @param string $class The full class name, ie `Cake\Core\App`.
* @return array{0: string, 1: string} Array with 2 indexes. 0 => namespace, 1 => classname.
*/
function namespaceSplit(string $class): array
{
$pos = strrpos($class, '\\');
if ($pos === false) {
return ['', $class];
}
return [substr($class, 0, $pos), substr($class, $pos + 1)];
}
}
if (!function_exists('Cake\Core\pr')) {
/**
* print_r() convenience function.
*
* In terminals this will act similar to using print_r() directly, when not run on CLI
* print_r() will also wrap `<pre>` tags around the output of given variable. Similar to debug().
*
* This function returns the same variable that was passed.
*
* @param mixed $var Variable to print out.
* @return mixed the same $var that was passed to this function
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pr
* @see debug()
*/
function pr(mixed $var): mixed
{
if (!Configure::read('debug')) {
return $var;
}
$template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '<pre class="pr">%s</pre>' : "\n%s\n\n";
printf($template, trim(print_r($var, true)));
return $var;
}
}
if (!function_exists('Cake\Core\pj')) {
/**
* JSON pretty print convenience function.
*
* In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI
* will also wrap `<pre>` tags around the output of given variable. Similar to pr().
*
* This function returns the same variable that was passed.
*
* @param mixed $var Variable to print out.
* @return mixed the same $var that was passed to this function
* @see pr()
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pj
*/
function pj(mixed $var): mixed
{
if (!Configure::read('debug')) {
return $var;
}
$template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '<pre class="pj">%s</pre>' : "\n%s\n\n";
$flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
printf($template, trim((string)json_encode($var, $flags)));
return $var;
}
}
if (!function_exists('Cake\Core\env')) {
/**
* Gets an environment variable from available sources, and provides emulation
* for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
* IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
* environment information.
*
* @param string $key Environment variable name.
* @param string|bool|null $default Specify a default value in case the environment variable is not defined.
* @return string|float|int|bool|null Environment variable setting.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#env
*/
function env(string $key, string|float|int|bool|null $default = null): string|float|int|bool|null
{
if ($key === 'HTTPS') {
if (isset($_SERVER['HTTPS'])) {
return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
}
return str_starts_with((string)env('SCRIPT_URI'), 'https://');
}
if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) {
$key = 'SCRIPT_URL';
}
$val = $_SERVER[$key] ?? $_ENV[$key] ?? null;
assert($val === null || is_scalar($val));
if ($val == null && getenv($key) !== false) {
$val = (string)getenv($key);
}
if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) {
$addr = env('HTTP_PC_REMOTE_ADDR');
if ($addr !== null) {
$val = $addr;
}
}
if ($val !== null) {
return $val;
}
switch ($key) {
case 'DOCUMENT_ROOT':
$name = (string)env('SCRIPT_NAME');
$filename = (string)env('SCRIPT_FILENAME');
$offset = 0;
if (!str_ends_with($name, '.php')) {
$offset = 4;
}
return substr($filename, 0, -(strlen($name) + $offset));
case 'PHP_SELF':
return str_replace((string)env('DOCUMENT_ROOT'), '', (string)env('SCRIPT_FILENAME'));
case 'CGI_MODE':
return PHP_SAPI === 'cgi';
}
return $default;
}
}
if (!function_exists('Cake\Core\triggerWarning')) {
/**
* Triggers an E_USER_WARNING.
*
* @param string $message The warning message.
* @return void
*/
function triggerWarning(string $message): void
{
trigger_error($message, E_USER_WARNING);
}
}
if (!function_exists('Cake\Core\deprecationWarning')) {
/**
* Helper method for outputting deprecation warnings
*
* @param string $version The version that added this deprecation warning.
* @param string $message The message to output as a deprecation warning.
* @param int $stackFrame The stack frame to include in the error. Defaults to 1
* as that should point to application/plugin code.
* @return void
*/
function deprecationWarning(string $version, string $message, int $stackFrame = 1): void
{
if (!(error_reporting() & E_USER_DEPRECATED)) {
return;
}
$trace = debug_backtrace();
if (isset($trace[$stackFrame])) {
$frame = $trace[$stackFrame];
$frame += ['file' => '[internal]', 'line' => '??'];
// Assuming we're installed in vendor/cakephp/cakephp/src/Core/functions.php
$root = dirname(__DIR__, 5);
if (defined('ROOT')) {
$root = ROOT;
}
$relative = str_replace(DIRECTORY_SEPARATOR, '/', substr($frame['file'], strlen($root) + 1));
$patterns = (array)Configure::read('Error.ignoredDeprecationPaths');
foreach ($patterns as $pattern) {
$pattern = str_replace(DIRECTORY_SEPARATOR, '/', $pattern);
if (fnmatch($pattern, $relative)) {
return;
}
}
$message = sprintf(
"Since %s: %s\n%s, line: %s\n" .
'You can disable all deprecation warnings by setting `Error.errorLevel` to ' .
'`E_ALL & ~E_USER_DEPRECATED`. Adding `%s` to `Error.ignoredDeprecationPaths` ' .
'in your `config/app.php` config will mute deprecations from that file only.',
$version,
$message,
$frame['file'],
$frame['line'],
$relative,
);
}
static $errors = [];
$checksum = hash('xxh128', $message);
$duplicate = (bool)Configure::read('Error.allowDuplicateDeprecations', false);
if (isset($errors[$checksum]) && !$duplicate) {
return;
}
if (!$duplicate) {
$errors[$checksum] = true;
}
trigger_error($message, E_USER_DEPRECATED);
}
}
if (!function_exists('Cake\Core\toString')) {
/**
* Converts the given value to a string.
*
* This method attempts to convert the given value to a string.
* If the value is already a string, it returns the value as it is.
* ``null`` is returned if the conversion is not possible.
*
* @param mixed $value The value to be converted.
* @return ?string Returns the string representation of the value, or null if the value is not a string.
* @since 5.1.0
*/
function toString(mixed $value): ?string
{
if (is_string($value)) {
return $value;
}
if (is_int($value)) {
return (string)$value;
}
if (is_bool($value)) {
return $value ? '1' : '0';
}
if (is_float($value)) {
if (is_nan($value) || is_infinite($value)) {
return null;
}
try {
$return = json_encode($value, JSON_THROW_ON_ERROR);
} catch (JsonException) {
$return = null;
}
if ($return === null || str_contains($return, 'e')) {
return rtrim(sprintf('%.' . (PHP_FLOAT_DIG + 3) . 'F', $value), '.0');
}
return $return;
}
if ($value instanceof Stringable) {
return (string)$value;
}
return null;
}
}
if (!function_exists('Cake\Core\toInt')) {
/**
* Converts a value to an integer.
*
* This method attempts to convert the given value to an integer.
* If the conversion is successful, it returns the value as an integer.
* If the conversion fails, it returns NULL.
*
* String values are trimmed using trim().
*
* @param mixed $value The value to be converted to an integer.
* @return int|null Returns the converted integer value or null if the conversion fails.
* @since 5.1.0
*/
function toInt(mixed $value): ?int
{
if (is_int($value)) {
return $value;
}
if (is_string($value)) {
$value = trim($value);
if (preg_match('/^0+[^0]{1}/', $value)) {
$value = ltrim($value, '0');
}
$value = filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
return $value === PHP_INT_MIN ? null : $value;
}
if (is_float($value)) {
if (is_nan($value) || is_infinite($value)) {
return null;
}
return (int)$value;
}
if (is_bool($value)) {
return (int)$value;
}
return null;
}
}
if (!function_exists('Cake\Core\toFloat')) {
/**
* Converts a value to a float.
*
* This method attempts to convert the given value to a float.
* If the conversion is successful, it returns the value as an float.
* If the conversion fails, it returns NULL.
*
* String values are trimmed using trim().
*
* @param mixed $value The value to be converted to a float.
* @return float|null Returns the converted float value or null if the conversion fails.
* @since 5.1.0
*/
function toFloat(mixed $value): ?float
{
if (is_string($value)) {
$value = trim($value);
if (preg_match('/^0+[^0]{1}/', $value)) {
$value = ltrim($value, '0');
}
$value = filter_var($value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
return $value === PHP_FLOAT_MIN ? null : $value;
}
if (is_float($value)) {
if (is_nan($value) || is_infinite($value)) {
return null;
}
return $value;
}
if (is_int($value)) {
return (float)$value;
}
if (is_bool($value)) {
return (float)$value;
}
return null;
}
}
if (!function_exists('Cake\Core\toBool')) {
/**
* Converts a value to boolean.
*
* 1 | '1' | 1.0 | true - values returns as true
* 0 | '0' | 0.0 | false - values returns as false
* Other values returns as null.
*
* @param mixed $value The value to convert to boolean.
* @return bool|null Returns true if the value is truthy, false if it's falsy, or NULL otherwise.
* @since 5.1.0
*/
function toBool(mixed $value): ?bool
{
if (in_array($value, ['1', 1, 1.0, true], true)) {
return true;
}
if (in_array($value, ['0', 0, 0.0, false], true)) {
return false;
}
return null;
}
}
+271
View File
@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
// phpcs:disable PSR1.Files.SideEffects
use function Cake\Core\deprecationWarning as cakeDeprecationWarning;
use function Cake\Core\env as cakeEnv;
use function Cake\Core\h as cakeH;
use function Cake\Core\namespaceSplit as cakeNamespaceSplit;
use function Cake\Core\pathCombine as cakePathCombine;
use function Cake\Core\pj as cakePj;
use function Cake\Core\pluginSplit as cakePluginSplit;
use function Cake\Core\pr as cakePr;
use function Cake\Core\toBool as cakeToBool;
use function Cake\Core\toFloat as cakeToFloat;
use function Cake\Core\toInt as cakeToInt;
use function Cake\Core\toString as cakeToString;
use function Cake\Core\triggerWarning as cakeTriggerWarning;
if (!function_exists('pathCombine')) {
/**
* Combines parts with a forward-slash `/`.
*
* Skips adding a forward-slash if either `/` or `\` already exists.
*
* @param array<string> $parts
* @param bool|null $trailing Determines how trailing slashes are handled
* - If true, ensures a trailing forward-slash is added if one doesn't exist
* - If false, ensures any trailing slash is removed
* - if null, ignores trailing slashes
* @return string
*/
function pathCombine(array $parts, ?bool $trailing = null): string
{
return cakePathCombine($parts, $trailing);
}
}
if (!function_exists('h')) {
/**
* Convenience method for htmlspecialchars.
*
* @param mixed $text Text to wrap through htmlspecialchars. Also works with arrays, and objects.
* Arrays will be mapped and have all their elements escaped. Objects will be string cast if they
* implement a `__toString` method. Otherwise, the class name will be used.
* Other scalar types will be returned unchanged.
* @param bool $double Encode existing html entities.
* @param string|null $charset Character set to use when escaping.
* Defaults to config value in `mb_internal_encoding()` or 'UTF-8'.
* @return mixed Wrapped text.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#h
*/
function h(mixed $text, bool $double = true, ?string $charset = null): mixed
{
return cakeH($text, $double, $charset);
}
}
if (!function_exists('pluginSplit')) {
/**
* Splits a dot syntax plugin name into its plugin and class name.
* If $name does not have a dot, then index 0 will be null.
*
* Commonly used like
* ```
* list($plugin, $name) = pluginSplit($name);
* ```
*
* @param string $name The name you want to plugin split.
* @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it.
* @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null.
* @return array Array with 2 indexes. 0 => plugin name, 1 => class name.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit
* @phpstan-return array{string|null, string}
*/
function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array
{
return cakePluginSplit($name, $dotAppend, $plugin);
}
}
if (!function_exists('namespaceSplit')) {
/**
* Split the namespace from the classname.
*
* Commonly used like `list($namespace, $className) = namespaceSplit($class);`.
*
* @param string $class The full class name, ie `Cake\Core\App`.
* @return array{0: string, 1: string} Array with 2 indexes. 0 => namespace, 1 => classname.
*/
function namespaceSplit(string $class): array
{
return cakeNamespaceSplit($class);
}
}
if (!function_exists('pr')) {
/**
* print_r() convenience function.
*
* In terminals this will act similar to using print_r() directly, when not run on CLI
* print_r() will also wrap `<pre>` tags around the output of given variable. Similar to debug().
*
* This function returns the same variable that was passed.
*
* @param mixed $var Variable to print out.
* @return mixed the same $var that was passed to this function
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pr
* @see debug()
*/
function pr(mixed $var): mixed
{
return cakePr($var);
}
}
if (!function_exists('pj')) {
/**
* JSON pretty print convenience function.
*
* In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI
* will also wrap `<pre>` tags around the output of given variable. Similar to pr().
*
* This function returns the same variable that was passed.
*
* @param mixed $var Variable to print out.
* @return mixed the same $var that was passed to this function
* @see pr()
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pj
*/
function pj(mixed $var): mixed
{
return cakePj($var);
}
}
if (!function_exists('env')) {
/**
* Gets an environment variable from available sources, and provides emulation
* for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
* IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
* environment information.
*
* @param string $key Environment variable name.
* @param string|bool|null $default Specify a default value in case the environment variable is not defined.
* @return string|float|int|bool|null Environment variable setting.
* @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#env
*/
function env(string $key, string|float|int|bool|null $default = null): string|float|int|bool|null
{
return cakeEnv($key, $default);
}
}
if (!function_exists('triggerWarning')) {
/**
* Triggers an E_USER_WARNING.
*
* @param string $message The warning message.
* @return void
*/
function triggerWarning(string $message): void
{
cakeTriggerWarning($message);
}
}
if (!function_exists('deprecationWarning')) {
/**
* Helper method for outputting deprecation warnings
*
* @param string $version The version that added this deprecation warning.
* @param string $message The message to output as a deprecation warning.
* @param int $stackFrame The stack frame to include in the error. Defaults to 1
* as that should point to application/plugin code.
* @return void
*/
function deprecationWarning(string $version, string $message, int $stackFrame = 1): void
{
cakeDeprecationWarning($version, $message, $stackFrame + 1);
}
}
if (!function_exists('toString')) {
/**
* Converts the given value to a string.
*
* This method attempts to convert the given value to a string.
* If the value is already a string, it returns the value as it is.
* ``null`` is returned if the conversion is not possible.
*
* @param mixed $value The value to be converted.
* @return ?string Returns the string representation of the value, or null if the value is not a string.
* @since 5.1.1
*/
function toString(mixed $value): ?string
{
return cakeToString($value);
}
}
if (!function_exists('toInt')) {
/**
* Converts a value to an integer.
*
* This method attempts to convert the given value to an integer.
* If the conversion is successful, it returns the value as an integer.
* If the conversion fails, it returns NULL.
*
* String values are trimmed using trim().
*
* @param mixed $value The value to be converted to an integer.
* @return int|null Returns the converted integer value or null if the conversion fails.
* @since 5.1.1
*/
function toInt(mixed $value): ?int
{
return cakeToInt($value);
}
}
if (!function_exists('toFloat')) {
/**
* Converts a value to a float.
*
* This method attempts to convert the given value to a float.
* If the conversion is successful, it returns the value as an float.
* If the conversion fails, it returns NULL.
*
* String values are trimmed using trim().
*
* @param mixed $value The value to be converted to a float.
* @return float|null Returns the converted float value or null if the conversion fails.
* @since 5.1.1
*/
function toFloat(mixed $value): ?float
{
return cakeToFloat($value);
}
}
if (!function_exists('toBool')) {
/**
* Converts a value to boolean.
*
* 1 | '1' | 1.0 | true - values returns as true
* 0 | '0' | 0.0 | false - values returns as false
* Other values returns as null.
*
* @param mixed $value The value to convert to boolean.
* @return bool|null Returns true if the value is truthy, false if it's falsy, or NULL otherwise.
* @since 5.1.1
*/
function toBool(mixed $value): ?bool
{
return cakeToBool($value);
}
}