├── App.php ├── BasePlugin.php ├── ClassLoader.php ├── Configure.php ├── Configure ├── ConfigEngineInterface.php ├── Engine │ ├── IniConfig.php │ ├── JsonConfig.php │ └── PhpConfig.php └── FileConfigTrait.php ├── ConsoleApplicationInterface.php ├── Container.php ├── ContainerApplicationInterface.php ├── ContainerInterface.php ├── ConventionsTrait.php ├── Exception ├── CakeException.php ├── Exception.php └── MissingPluginException.php ├── HttpApplicationInterface.php ├── InstanceConfigTrait.php ├── LICENSE.txt ├── ObjectRegistry.php ├── Plugin.php ├── PluginApplicationInterface.php ├── PluginCollection.php ├── PluginInterface.php ├── README.md ├── Retry ├── CommandRetry.php └── RetryStrategyInterface.php ├── ServiceConfig.php ├── ServiceProvider.php ├── StaticConfigTrait.php ├── composer.json └── functions.php /App.php: -------------------------------------------------------------------------------- 1 | {"{$key}Enabled"} = (bool)$options[$key]; 112 | } 113 | } 114 | foreach (['name', 'path', 'classPath', 'configPath', 'templatePath'] as $path) { 115 | if (isset($options[$path])) { 116 | $this->{$path} = $options[$path]; 117 | } 118 | } 119 | 120 | $this->initialize(); 121 | } 122 | 123 | /** 124 | * Initialization hook called from constructor. 125 | * 126 | * @return void 127 | */ 128 | public function initialize(): void 129 | { 130 | } 131 | 132 | /** 133 | * @inheritDoc 134 | */ 135 | public function getName(): string 136 | { 137 | if ($this->name) { 138 | return $this->name; 139 | } 140 | $parts = explode('\\', static::class); 141 | array_pop($parts); 142 | $this->name = implode('/', $parts); 143 | 144 | return $this->name; 145 | } 146 | 147 | /** 148 | * @inheritDoc 149 | */ 150 | public function getPath(): string 151 | { 152 | if ($this->path) { 153 | return $this->path; 154 | } 155 | $reflection = new ReflectionClass($this); 156 | $path = dirname($reflection->getFileName()); 157 | 158 | // Trim off src 159 | if (substr($path, -3) === 'src') { 160 | $path = substr($path, 0, -3); 161 | } 162 | $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 163 | 164 | return $this->path; 165 | } 166 | 167 | /** 168 | * @inheritDoc 169 | */ 170 | public function getConfigPath(): string 171 | { 172 | if ($this->configPath) { 173 | return $this->configPath; 174 | } 175 | $path = $this->getPath(); 176 | 177 | return $path . 'config' . DIRECTORY_SEPARATOR; 178 | } 179 | 180 | /** 181 | * @inheritDoc 182 | */ 183 | public function getClassPath(): string 184 | { 185 | if ($this->classPath) { 186 | return $this->classPath; 187 | } 188 | $path = $this->getPath(); 189 | 190 | return $path . 'src' . DIRECTORY_SEPARATOR; 191 | } 192 | 193 | /** 194 | * @inheritDoc 195 | */ 196 | public function getTemplatePath(): string 197 | { 198 | if ($this->templatePath) { 199 | return $this->templatePath; 200 | } 201 | $path = $this->getPath(); 202 | 203 | return $this->templatePath = $path . 'templates' . DIRECTORY_SEPARATOR; 204 | } 205 | 206 | /** 207 | * @inheritDoc 208 | */ 209 | public function enable(string $hook) 210 | { 211 | $this->checkHook($hook); 212 | $this->{"{$hook}Enabled}"} = true; 213 | 214 | return $this; 215 | } 216 | 217 | /** 218 | * @inheritDoc 219 | */ 220 | public function disable(string $hook) 221 | { 222 | $this->checkHook($hook); 223 | $this->{"{$hook}Enabled"} = false; 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * @inheritDoc 230 | */ 231 | public function isEnabled(string $hook): bool 232 | { 233 | $this->checkHook($hook); 234 | 235 | return $this->{"{$hook}Enabled"} === true; 236 | } 237 | 238 | /** 239 | * Check if a hook name is valid 240 | * 241 | * @param string $hook The hook name to check 242 | * @throws \InvalidArgumentException on invalid hooks 243 | * @return void 244 | */ 245 | protected function checkHook(string $hook): void 246 | { 247 | if (!in_array($hook, static::VALID_HOOKS, true)) { 248 | throw new InvalidArgumentException( 249 | "`$hook` is not a valid hook name. Must be one of " . implode(', ', static::VALID_HOOKS) 250 | ); 251 | } 252 | } 253 | 254 | /** 255 | * @inheritDoc 256 | */ 257 | public function routes(RouteBuilder $routes): void 258 | { 259 | $path = $this->getConfigPath() . 'routes.php'; 260 | if (is_file($path)) { 261 | require $path; 262 | } 263 | } 264 | 265 | /** 266 | * @inheritDoc 267 | */ 268 | public function bootstrap(PluginApplicationInterface $app): void 269 | { 270 | $bootstrap = $this->getConfigPath() . 'bootstrap.php'; 271 | if (is_file($bootstrap)) { 272 | require $bootstrap; 273 | } 274 | } 275 | 276 | /** 277 | * @inheritDoc 278 | */ 279 | public function console(CommandCollection $commands): CommandCollection 280 | { 281 | return $commands->addMany($commands->discoverPlugin($this->getName())); 282 | } 283 | 284 | /** 285 | * @inheritDoc 286 | */ 287 | public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue 288 | { 289 | return $middlewareQueue; 290 | } 291 | 292 | /** 293 | * Register container services for this plugin. 294 | * 295 | * @param \Cake\Core\ContainerInterface $container The container to add services to. 296 | * @return void 297 | */ 298 | public function services(ContainerInterface $container): void 299 | { 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /ClassLoader.php: -------------------------------------------------------------------------------- 1 | _prefixes[$prefix])) { 62 | $this->_prefixes[$prefix] = []; 63 | } 64 | 65 | if ($prepend) { 66 | array_unshift($this->_prefixes[$prefix], $baseDir); 67 | } else { 68 | $this->_prefixes[$prefix][] = $baseDir; 69 | } 70 | } 71 | 72 | /** 73 | * Loads the class file for a given class name. 74 | * 75 | * @param string $class The fully-qualified class name. 76 | * @return string|false The mapped file name on success, or boolean false on 77 | * failure. 78 | */ 79 | public function loadClass(string $class) 80 | { 81 | $prefix = $class; 82 | 83 | while (($pos = strrpos($prefix, '\\')) !== false) { 84 | $prefix = substr($class, 0, $pos + 1); 85 | $relativeClass = substr($class, $pos + 1); 86 | 87 | $mappedFile = $this->_loadMappedFile($prefix, $relativeClass); 88 | if ($mappedFile) { 89 | return $mappedFile; 90 | } 91 | 92 | $prefix = rtrim($prefix, '\\'); 93 | } 94 | 95 | return false; 96 | } 97 | 98 | /** 99 | * Load the mapped file for a namespace prefix and relative class. 100 | * 101 | * @param string $prefix The namespace prefix. 102 | * @param string $relativeClass The relative class name. 103 | * @return string|false Boolean false if no mapped file can be loaded, or the 104 | * name of the mapped file that was loaded. 105 | */ 106 | protected function _loadMappedFile(string $prefix, string $relativeClass) 107 | { 108 | if (!isset($this->_prefixes[$prefix])) { 109 | return false; 110 | } 111 | 112 | foreach ($this->_prefixes[$prefix] as $baseDir) { 113 | $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php'; 114 | 115 | if ($this->_requireFile($file)) { 116 | return $file; 117 | } 118 | } 119 | 120 | return false; 121 | } 122 | 123 | /** 124 | * If a file exists, require it from the file system. 125 | * 126 | * @param string $file The file to require. 127 | * @return bool True if the file exists, false if not. 128 | */ 129 | protected function _requireFile(string $file): bool 130 | { 131 | if (file_exists($file)) { 132 | require $file; 133 | 134 | return true; 135 | } 136 | 137 | return false; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Configure.php: -------------------------------------------------------------------------------- 1 | false, 44 | ]; 45 | 46 | /** 47 | * Configured engine classes, used to load config files from resources 48 | * 49 | * @see \Cake\Core\Configure::load() 50 | * @var \Cake\Core\Configure\ConfigEngineInterface[] 51 | */ 52 | protected static $_engines = []; 53 | 54 | /** 55 | * Flag to track whether or not ini_set exists. 56 | * 57 | * @var bool|null 58 | */ 59 | protected static $_hasIniSet; 60 | 61 | /** 62 | * Used to store a dynamic variable in Configure. 63 | * 64 | * Usage: 65 | * ``` 66 | * Configure::write('One.key1', 'value of the Configure::One[key1]'); 67 | * Configure::write(['One.key1' => 'value of the Configure::One[key1]']); 68 | * Configure::write('One', [ 69 | * 'key1' => 'value of the Configure::One[key1]', 70 | * 'key2' => 'value of the Configure::One[key2]' 71 | * ]); 72 | * 73 | * Configure::write([ 74 | * 'One.key1' => 'value of the Configure::One[key1]', 75 | * 'One.key2' => 'value of the Configure::One[key2]' 76 | * ]); 77 | * ``` 78 | * 79 | * @param string|array $config The key to write, can be a dot notation value. 80 | * Alternatively can be an array containing key(s) and value(s). 81 | * @param mixed $value Value to set for var 82 | * @return void 83 | * @link https://book.cakephp.org/4/en/development/configuration.html#writing-configuration-data 84 | */ 85 | public static function write($config, $value = null): void 86 | { 87 | if (!is_array($config)) { 88 | $config = [$config => $value]; 89 | } 90 | 91 | foreach ($config as $name => $value) { 92 | static::$_values = Hash::insert(static::$_values, $name, $value); 93 | } 94 | 95 | if (isset($config['debug'])) { 96 | if (static::$_hasIniSet === null) { 97 | static::$_hasIniSet = function_exists('ini_set'); 98 | } 99 | if (static::$_hasIniSet) { 100 | ini_set('display_errors', $config['debug'] ? '1' : '0'); 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * Used to read information stored in Configure. It's not 107 | * possible to store `null` values in Configure. 108 | * 109 | * Usage: 110 | * ``` 111 | * Configure::read('Name'); will return all values for Name 112 | * Configure::read('Name.key'); will return only the value of Configure::Name[key] 113 | * ``` 114 | * 115 | * @param string|null $var Variable to obtain. Use '.' to access array elements. 116 | * @param mixed $default The return value when the configure does not exist 117 | * @return mixed Value stored in configure, or null. 118 | * @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data 119 | */ 120 | public static function read(?string $var = null, $default = null) 121 | { 122 | if ($var === null) { 123 | return static::$_values; 124 | } 125 | 126 | return Hash::get(static::$_values, $var, $default); 127 | } 128 | 129 | /** 130 | * Returns true if given variable is set in Configure. 131 | * 132 | * @param string $var Variable name to check for 133 | * @return bool True if variable is there 134 | */ 135 | public static function check(string $var): bool 136 | { 137 | if (empty($var)) { 138 | return false; 139 | } 140 | 141 | return static::read($var) !== null; 142 | } 143 | 144 | /** 145 | * Used to get information stored in Configure. It's not 146 | * possible to store `null` values in Configure. 147 | * 148 | * Acts as a wrapper around Configure::read() and Configure::check(). 149 | * The configure key/value pair fetched via this method is expected to exist. 150 | * In case it does not an exception will be thrown. 151 | * 152 | * Usage: 153 | * ``` 154 | * Configure::readOrFail('Name'); will return all values for Name 155 | * Configure::readOrFail('Name.key'); will return only the value of Configure::Name[key] 156 | * ``` 157 | * 158 | * @param string $var Variable to obtain. Use '.' to access array elements. 159 | * @return mixed Value stored in configure. 160 | * @throws \RuntimeException if the requested configuration is not set. 161 | * @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data 162 | */ 163 | public static function readOrFail(string $var) 164 | { 165 | if (!static::check($var)) { 166 | throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var)); 167 | } 168 | 169 | return static::read($var); 170 | } 171 | 172 | /** 173 | * Used to delete a variable from Configure. 174 | * 175 | * Usage: 176 | * ``` 177 | * Configure::delete('Name'); will delete the entire Configure::Name 178 | * Configure::delete('Name.key'); will delete only the Configure::Name[key] 179 | * ``` 180 | * 181 | * @param string $var the var to be deleted 182 | * @return void 183 | * @link https://book.cakephp.org/4/en/development/configuration.html#deleting-configuration-data 184 | */ 185 | public static function delete(string $var): void 186 | { 187 | static::$_values = Hash::remove(static::$_values, $var); 188 | } 189 | 190 | /** 191 | * Used to consume information stored in Configure. It's not 192 | * possible to store `null` values in Configure. 193 | * 194 | * Acts as a wrapper around Configure::consume() and Configure::check(). 195 | * The configure key/value pair consumed via this method is expected to exist. 196 | * In case it does not an exception will be thrown. 197 | * 198 | * @param string $var Variable to consume. Use '.' to access array elements. 199 | * @return mixed Value stored in configure. 200 | * @throws \RuntimeException if the requested configuration is not set. 201 | * @since 3.6.0 202 | */ 203 | public static function consumeOrFail(string $var) 204 | { 205 | if (!static::check($var)) { 206 | throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var)); 207 | } 208 | 209 | return static::consume($var); 210 | } 211 | 212 | /** 213 | * Used to read and delete a variable from Configure. 214 | * 215 | * This is primarily used during bootstrapping to move configuration data 216 | * out of configure into the various other classes in CakePHP. 217 | * 218 | * @param string $var The key to read and remove. 219 | * @return array|string|null 220 | */ 221 | public static function consume(string $var) 222 | { 223 | if (strpos($var, '.') === false) { 224 | if (!isset(static::$_values[$var])) { 225 | return null; 226 | } 227 | $value = static::$_values[$var]; 228 | unset(static::$_values[$var]); 229 | 230 | return $value; 231 | } 232 | $value = Hash::get(static::$_values, $var); 233 | static::delete($var); 234 | 235 | return $value; 236 | } 237 | 238 | /** 239 | * Add a new engine to Configure. Engines allow you to read configuration 240 | * files in various formats/storage locations. CakePHP comes with two built-in engines 241 | * PhpConfig and IniConfig. You can also implement your own engine classes in your application. 242 | * 243 | * To add a new engine to Configure: 244 | * 245 | * ``` 246 | * Configure::config('ini', new IniConfig()); 247 | * ``` 248 | * 249 | * @param string $name The name of the engine being configured. This alias is used later to 250 | * read values from a specific engine. 251 | * @param \Cake\Core\Configure\ConfigEngineInterface $engine The engine to append. 252 | * @return void 253 | */ 254 | public static function config(string $name, ConfigEngineInterface $engine): void 255 | { 256 | static::$_engines[$name] = $engine; 257 | } 258 | 259 | /** 260 | * Returns true if the Engine objects is configured. 261 | * 262 | * @param string $name Engine name. 263 | * @return bool 264 | */ 265 | public static function isConfigured(string $name): bool 266 | { 267 | return isset(static::$_engines[$name]); 268 | } 269 | 270 | /** 271 | * Gets the names of the configured Engine objects. 272 | * 273 | * @return string[] 274 | */ 275 | public static function configured(): array 276 | { 277 | $engines = array_keys(static::$_engines); 278 | 279 | return array_map(function ($key) { 280 | return (string)$key; 281 | }, $engines); 282 | } 283 | 284 | /** 285 | * Remove a configured engine. This will unset the engine 286 | * and make any future attempts to use it cause an Exception. 287 | * 288 | * @param string $name Name of the engine to drop. 289 | * @return bool Success 290 | */ 291 | public static function drop(string $name): bool 292 | { 293 | if (!isset(static::$_engines[$name])) { 294 | return false; 295 | } 296 | unset(static::$_engines[$name]); 297 | 298 | return true; 299 | } 300 | 301 | /** 302 | * Loads stored configuration information from a resource. You can add 303 | * config file resource engines with `Configure::config()`. 304 | * 305 | * Loaded configuration information will be merged with the current 306 | * runtime configuration. You can load configuration files from plugins 307 | * by preceding the filename with the plugin name. 308 | * 309 | * `Configure::load('Users.user', 'default')` 310 | * 311 | * Would load the 'user' config file using the default config engine. You can load 312 | * app config files by giving the name of the resource you want loaded. 313 | * 314 | * ``` 315 | * Configure::load('setup', 'default'); 316 | * ``` 317 | * 318 | * If using `default` config and no engine has been configured for it yet, 319 | * one will be automatically created using PhpConfig 320 | * 321 | * @param string $key name of configuration resource to load. 322 | * @param string $config Name of the configured engine to use to read the resource identified by $key. 323 | * @param bool $merge if config files should be merged instead of simply overridden 324 | * @return bool False if file not found, true if load successful. 325 | * @link https://book.cakephp.org/4/en/development/configuration.html#reading-and-writing-configuration-files 326 | */ 327 | public static function load(string $key, string $config = 'default', bool $merge = true): bool 328 | { 329 | $engine = static::_getEngine($config); 330 | if (!$engine) { 331 | return false; 332 | } 333 | $values = $engine->read($key); 334 | 335 | if ($merge) { 336 | $values = Hash::merge(static::$_values, $values); 337 | } 338 | 339 | static::write($values); 340 | 341 | return true; 342 | } 343 | 344 | /** 345 | * Dump data currently in Configure into $key. The serialization format 346 | * is decided by the config engine attached as $config. For example, if the 347 | * 'default' adapter is a PhpConfig, the generated file will be a PHP 348 | * configuration file loadable by the PhpConfig. 349 | * 350 | * ### Usage 351 | * 352 | * Given that the 'default' engine is an instance of PhpConfig. 353 | * Save all data in Configure to the file `my_config.php`: 354 | * 355 | * ``` 356 | * Configure::dump('my_config', 'default'); 357 | * ``` 358 | * 359 | * Save only the error handling configuration: 360 | * 361 | * ``` 362 | * Configure::dump('error', 'default', ['Error', 'Exception']; 363 | * ``` 364 | * 365 | * @param string $key The identifier to create in the config adapter. 366 | * This could be a filename or a cache key depending on the adapter being used. 367 | * @param string $config The name of the configured adapter to dump data with. 368 | * @param string[] $keys The name of the top-level keys you want to dump. 369 | * This allows you save only some data stored in Configure. 370 | * @return bool Success 371 | * @throws \Cake\Core\Exception\CakeException if the adapter does not implement a `dump` method. 372 | */ 373 | public static function dump(string $key, string $config = 'default', array $keys = []): bool 374 | { 375 | $engine = static::_getEngine($config); 376 | if (!$engine) { 377 | throw new CakeException(sprintf('There is no "%s" config engine.', $config)); 378 | } 379 | $values = static::$_values; 380 | if (!empty($keys)) { 381 | $values = array_intersect_key($values, array_flip($keys)); 382 | } 383 | 384 | return $engine->dump($key, $values); 385 | } 386 | 387 | /** 388 | * Get the configured engine. Internally used by `Configure::load()` and `Configure::dump()` 389 | * Will create new PhpConfig for default if not configured yet. 390 | * 391 | * @param string $config The name of the configured adapter 392 | * @return \Cake\Core\Configure\ConfigEngineInterface|null Engine instance or null 393 | */ 394 | protected static function _getEngine(string $config): ?ConfigEngineInterface 395 | { 396 | if (!isset(static::$_engines[$config])) { 397 | if ($config !== 'default') { 398 | return null; 399 | } 400 | static::config($config, new PhpConfig()); 401 | } 402 | 403 | return static::$_engines[$config]; 404 | } 405 | 406 | /** 407 | * Used to determine the current version of CakePHP. 408 | * 409 | * Usage 410 | * ``` 411 | * Configure::version(); 412 | * ``` 413 | * 414 | * @return string Current version of CakePHP 415 | */ 416 | public static function version(): string 417 | { 418 | $version = static::read('Cake.version'); 419 | if ($version !== null) { 420 | return $version; 421 | } 422 | 423 | $path = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'config/config.php'; 424 | if (is_file($path)) { 425 | $config = require $path; 426 | static::write($config); 427 | 428 | return static::read('Cake.version'); 429 | } 430 | 431 | return 'unknown'; 432 | } 433 | 434 | /** 435 | * Used to write runtime configuration into Cache. Stored runtime configuration can be 436 | * restored using `Configure::restore()`. These methods can be used to enable configuration managers 437 | * frontends, or other GUI type interfaces for configuration. 438 | * 439 | * @param string $name The storage name for the saved configuration. 440 | * @param string $cacheConfig The cache configuration to save into. Defaults to 'default' 441 | * @param array|null $data Either an array of data to store, or leave empty to store all values. 442 | * @return bool Success 443 | * @throws \RuntimeException 444 | */ 445 | public static function store(string $name, string $cacheConfig = 'default', ?array $data = null): bool 446 | { 447 | if ($data === null) { 448 | $data = static::$_values; 449 | } 450 | if (!class_exists(Cache::class)) { 451 | throw new RuntimeException('You must install cakephp/cache to use Configure::store()'); 452 | } 453 | 454 | return Cache::write($name, $data, $cacheConfig); 455 | } 456 | 457 | /** 458 | * Restores configuration data stored in the Cache into configure. Restored 459 | * values will overwrite existing ones. 460 | * 461 | * @param string $name Name of the stored config file to load. 462 | * @param string $cacheConfig Name of the Cache configuration to read from. 463 | * @return bool Success. 464 | * @throws \RuntimeException 465 | */ 466 | public static function restore(string $name, string $cacheConfig = 'default'): bool 467 | { 468 | if (!class_exists(Cache::class)) { 469 | throw new RuntimeException('You must install cakephp/cache to use Configure::restore()'); 470 | } 471 | $values = Cache::read($name, $cacheConfig); 472 | if ($values) { 473 | static::write($values); 474 | 475 | return true; 476 | } 477 | 478 | return false; 479 | } 480 | 481 | /** 482 | * Clear all values stored in Configure. 483 | * 484 | * @return void 485 | */ 486 | public static function clear(): void 487 | { 488 | static::$_values = []; 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /Configure/ConfigEngineInterface.php: -------------------------------------------------------------------------------- 1 | ['password' => 'secret']]` 34 | * 35 | * You can nest properties as deeply as needed using `.`'s. In addition to using `.` you 36 | * can use standard ini section notation to create nested structures: 37 | * 38 | * ``` 39 | * [section] 40 | * key = value 41 | * ``` 42 | * 43 | * Once loaded into Configure, the above would be accessed using: 44 | * 45 | * `Configure::read('section.key'); 46 | * 47 | * You can also use `.` separated values in section names to create more deeply 48 | * nested structures. 49 | * 50 | * IniConfig also manipulates how the special ini values of 51 | * 'yes', 'no', 'on', 'off', 'null' are handled. These values will be 52 | * converted to their boolean equivalents. 53 | * 54 | * @see https://secure.php.net/parse_ini_file 55 | */ 56 | class IniConfig implements ConfigEngineInterface 57 | { 58 | use FileConfigTrait; 59 | 60 | /** 61 | * File extension. 62 | * 63 | * @var string 64 | */ 65 | protected $_extension = '.ini'; 66 | 67 | /** 68 | * The section to read, if null all sections will be read. 69 | * 70 | * @var string|null 71 | */ 72 | protected $_section; 73 | 74 | /** 75 | * Build and construct a new ini file parser. The parser can be used to read 76 | * ini files that are on the filesystem. 77 | * 78 | * @param string|null $path Path to load ini config files from. Defaults to CONFIG. 79 | * @param string|null $section Only get one section, leave null to parse and fetch 80 | * all sections in the ini file. 81 | */ 82 | public function __construct(?string $path = null, ?string $section = null) 83 | { 84 | if ($path === null) { 85 | $path = CONFIG; 86 | } 87 | $this->_path = $path; 88 | $this->_section = $section; 89 | } 90 | 91 | /** 92 | * Read an ini file and return the results as an array. 93 | * 94 | * @param string $key The identifier to read from. If the key has a . it will be treated 95 | * as a plugin prefix. The chosen file must be on the engine's path. 96 | * @return array Parsed configuration values. 97 | * @throws \Cake\Core\Exception\CakeException when files don't exist. 98 | * Or when files contain '..' as this could lead to abusive reads. 99 | */ 100 | public function read(string $key): array 101 | { 102 | $file = $this->_getFilePath($key, true); 103 | 104 | $contents = parse_ini_file($file, true); 105 | if ($this->_section && isset($contents[$this->_section])) { 106 | $values = $this->_parseNestedValues($contents[$this->_section]); 107 | } else { 108 | $values = []; 109 | foreach ($contents as $section => $attribs) { 110 | if (is_array($attribs)) { 111 | $values[$section] = $this->_parseNestedValues($attribs); 112 | } else { 113 | $parse = $this->_parseNestedValues([$attribs]); 114 | $values[$section] = array_shift($parse); 115 | } 116 | } 117 | } 118 | 119 | return $values; 120 | } 121 | 122 | /** 123 | * parses nested values out of keys. 124 | * 125 | * @param array $values Values to be exploded. 126 | * @return array Array of values exploded 127 | */ 128 | protected function _parseNestedValues(array $values): array 129 | { 130 | foreach ($values as $key => $value) { 131 | if ($value === '1') { 132 | $value = true; 133 | } 134 | if ($value === '') { 135 | $value = false; 136 | } 137 | unset($values[$key]); 138 | if (strpos((string)$key, '.') !== false) { 139 | $values = Hash::insert($values, $key, $value); 140 | } else { 141 | $values[$key] = $value; 142 | } 143 | } 144 | 145 | return $values; 146 | } 147 | 148 | /** 149 | * Dumps the state of Configure data into an ini formatted string. 150 | * 151 | * @param string $key The identifier to write to. If the key has a . it will be treated 152 | * as a plugin prefix. 153 | * @param array $data The data to convert to ini file. 154 | * @return bool Success. 155 | */ 156 | public function dump(string $key, array $data): bool 157 | { 158 | $result = []; 159 | foreach ($data as $k => $value) { 160 | $isSection = false; 161 | /** @psalm-suppress InvalidArrayAccess */ 162 | if ($k[0] !== '[') { 163 | $result[] = "[$k]"; 164 | $isSection = true; 165 | } 166 | if (is_array($value)) { 167 | $kValues = Hash::flatten($value, '.'); 168 | foreach ($kValues as $k2 => $v) { 169 | $result[] = "$k2 = " . $this->_value($v); 170 | } 171 | } 172 | if ($isSection) { 173 | $result[] = ''; 174 | } 175 | } 176 | $contents = trim(implode("\n", $result)); 177 | 178 | $filename = $this->_getFilePath($key); 179 | 180 | return file_put_contents($filename, $contents) > 0; 181 | } 182 | 183 | /** 184 | * Converts a value into the ini equivalent 185 | * 186 | * @param mixed $value Value to export. 187 | * @return string String value for ini file. 188 | */ 189 | protected function _value($value): string 190 | { 191 | if ($value === null) { 192 | return 'null'; 193 | } 194 | if ($value === true) { 195 | return 'true'; 196 | } 197 | if ($value === false) { 198 | return 'false'; 199 | } 200 | 201 | return (string)$value; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Configure/Engine/JsonConfig.php: -------------------------------------------------------------------------------- 1 | _path = $path; 63 | } 64 | 65 | /** 66 | * Read a config file and return its contents. 67 | * 68 | * Files with `.` in the name will be treated as values in plugins. Instead of 69 | * reading from the initialized path, plugin keys will be located using Plugin::path(). 70 | * 71 | * @param string $key The identifier to read from. If the key has a . it will be treated 72 | * as a plugin prefix. 73 | * @return array Parsed configuration values. 74 | * @throws \Cake\Core\Exception\CakeException When files don't exist or when 75 | * files contain '..' (as this could lead to abusive reads) or when there 76 | * is an error parsing the JSON string. 77 | */ 78 | public function read(string $key): array 79 | { 80 | $file = $this->_getFilePath($key, true); 81 | 82 | $values = json_decode(file_get_contents($file), true); 83 | if (json_last_error() !== JSON_ERROR_NONE) { 84 | throw new CakeException(sprintf( 85 | 'Error parsing JSON string fetched from config file "%s.json": %s', 86 | $key, 87 | json_last_error_msg() 88 | )); 89 | } 90 | if (!is_array($values)) { 91 | throw new CakeException(sprintf( 92 | 'Decoding JSON config file "%s.json" did not return an array', 93 | $key 94 | )); 95 | } 96 | 97 | return $values; 98 | } 99 | 100 | /** 101 | * Converts the provided $data into a JSON string that can be used saved 102 | * into a file and loaded later. 103 | * 104 | * @param string $key The identifier to write to. If the key has a . it will 105 | * be treated as a plugin prefix. 106 | * @param array $data Data to dump. 107 | * @return bool Success 108 | */ 109 | public function dump(string $key, array $data): bool 110 | { 111 | $filename = $this->_getFilePath($key); 112 | 113 | return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT)) > 0; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Configure/Engine/PhpConfig.php: -------------------------------------------------------------------------------- 1 | false, 36 | * 'Security' => [ 37 | * 'salt' => 'its-secret' 38 | * ], 39 | * 'App' => [ 40 | * 'namespace' => 'App' 41 | * ] 42 | * ]; 43 | * ``` 44 | * 45 | * @see \Cake\Core\Configure::load() for how to load custom configuration files. 46 | */ 47 | class PhpConfig implements ConfigEngineInterface 48 | { 49 | use FileConfigTrait; 50 | 51 | /** 52 | * File extension. 53 | * 54 | * @var string 55 | */ 56 | protected $_extension = '.php'; 57 | 58 | /** 59 | * Constructor for PHP Config file reading. 60 | * 61 | * @param string|null $path The path to read config files from. Defaults to CONFIG. 62 | */ 63 | public function __construct(?string $path = null) 64 | { 65 | if ($path === null) { 66 | $path = CONFIG; 67 | } 68 | $this->_path = $path; 69 | } 70 | 71 | /** 72 | * Read a config file and return its contents. 73 | * 74 | * Files with `.` in the name will be treated as values in plugins. Instead of 75 | * reading from the initialized path, plugin keys will be located using Plugin::path(). 76 | * 77 | * @param string $key The identifier to read from. If the key has a . it will be treated 78 | * as a plugin prefix. 79 | * @return array Parsed configuration values. 80 | * @throws \Cake\Core\Exception\CakeException when files don't exist or they don't contain `$config`. 81 | * Or when files contain '..' as this could lead to abusive reads. 82 | */ 83 | public function read(string $key): array 84 | { 85 | $file = $this->_getFilePath($key, true); 86 | 87 | $config = null; 88 | 89 | $return = include $file; 90 | if (is_array($return)) { 91 | return $return; 92 | } 93 | 94 | throw new CakeException(sprintf('Config file "%s" did not return an array', $key . '.php')); 95 | } 96 | 97 | /** 98 | * Converts the provided $data into a string of PHP code that can 99 | * be used saved into a file and loaded later. 100 | * 101 | * @param string $key The identifier to write to. If the key has a . it will be treated 102 | * as a plugin prefix. 103 | * @param array $data Data to dump. 104 | * @return bool Success 105 | */ 106 | public function dump(string $key, array $data): bool 107 | { 108 | $contents = '_getFilePath($key); 111 | 112 | return file_put_contents($filename, $contents) > 0; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Configure/FileConfigTrait.php: -------------------------------------------------------------------------------- 1 | _path . $key; 56 | } 57 | 58 | $file .= $this->_extension; 59 | 60 | if (!$checkExists || is_file($file)) { 61 | return $file; 62 | } 63 | 64 | $realPath = realpath($file); 65 | if ($realPath !== false && is_file($realPath)) { 66 | return $realPath; 67 | } 68 | 69 | throw new CakeException(sprintf('Could not load configuration file: %s', $file)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ConsoleApplicationInterface.php: -------------------------------------------------------------------------------- 1 | _attributes = $message; 71 | $message = vsprintf($this->_messageTemplate, $message); 72 | } 73 | parent::__construct($message, $code ?? $this->_defaultCode, $previous); 74 | } 75 | 76 | /** 77 | * Get the passed in attributes 78 | * 79 | * @return array 80 | */ 81 | public function getAttributes(): array 82 | { 83 | return $this->_attributes; 84 | } 85 | 86 | /** 87 | * Get/set the response header to be used 88 | * 89 | * See also Cake\Http\Response::withHeader() 90 | * 91 | * @param string|array|null $header A single header string or an associative 92 | * array of "header name" => "header value" 93 | * @param string|null $value The header value. 94 | * @return array|null 95 | * @deprecated 4.2.0 Use `HttpException::setHeaders()` instead. Response headers 96 | * should be set for HttpException only. 97 | */ 98 | public function responseHeader($header = null, $value = null): ?array 99 | { 100 | if ($header === null) { 101 | return $this->_responseHeaders; 102 | } 103 | 104 | deprecationWarning( 105 | 'Setting HTTP response headers from Exception directly is deprecated. ' . 106 | 'If your exceptions extend Exception, they must now extend HttpException. ' . 107 | 'You should only set HTTP headers on HttpException instances via the `setHeaders()` method.' 108 | ); 109 | if (is_array($header)) { 110 | return $this->_responseHeaders = $header; 111 | } 112 | 113 | return $this->_responseHeaders = [$header => $value]; 114 | } 115 | } 116 | 117 | // phpcs:disable 118 | class_exists('Cake\Core\Exception\CakeException'); 119 | // phpcs:enable 120 | -------------------------------------------------------------------------------- /Exception/Exception.php: -------------------------------------------------------------------------------- 1 | setConfig('key', $value); 53 | * ``` 54 | * 55 | * Setting a nested value: 56 | * 57 | * ``` 58 | * $this->setConfig('some.nested.key', $value); 59 | * ``` 60 | * 61 | * Updating multiple config settings at the same time: 62 | * 63 | * ``` 64 | * $this->setConfig(['one' => 'value', 'another' => 'value']); 65 | * ``` 66 | * 67 | * @param string|array $key The key to set, or a complete array of configs. 68 | * @param mixed|null $value The value to set. 69 | * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true. 70 | * @return $this 71 | * @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid. 72 | */ 73 | public function setConfig($key, $value = null, $merge = true) 74 | { 75 | if (!$this->_configInitialized) { 76 | $this->_config = $this->_defaultConfig; 77 | $this->_configInitialized = true; 78 | } 79 | 80 | $this->_configWrite($key, $value, $merge); 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Returns the config. 87 | * 88 | * ### Usage 89 | * 90 | * Reading the whole config: 91 | * 92 | * ``` 93 | * $this->getConfig(); 94 | * ``` 95 | * 96 | * Reading a specific value: 97 | * 98 | * ``` 99 | * $this->getConfig('key'); 100 | * ``` 101 | * 102 | * Reading a nested value: 103 | * 104 | * ``` 105 | * $this->getConfig('some.nested.key'); 106 | * ``` 107 | * 108 | * Reading with default value: 109 | * 110 | * ``` 111 | * $this->getConfig('some-key', 'default-value'); 112 | * ``` 113 | * 114 | * @param string|null $key The key to get or null for the whole config. 115 | * @param mixed $default The return value when the key does not exist. 116 | * @return mixed Configuration data at the named key or null if the key does not exist. 117 | */ 118 | public function getConfig(?string $key = null, $default = null) 119 | { 120 | if (!$this->_configInitialized) { 121 | $this->_config = $this->_defaultConfig; 122 | $this->_configInitialized = true; 123 | } 124 | 125 | $return = $this->_configRead($key); 126 | 127 | return $return ?? $default; 128 | } 129 | 130 | /** 131 | * Returns the config for this specific key. 132 | * 133 | * The config value for this key must exist, it can never be null. 134 | * 135 | * @param string $key The key to get. 136 | * @return mixed Configuration data at the named key 137 | * @throws \InvalidArgumentException 138 | */ 139 | public function getConfigOrFail(string $key) 140 | { 141 | $config = $this->getConfig($key); 142 | if ($config === null) { 143 | throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key)); 144 | } 145 | 146 | return $config; 147 | } 148 | 149 | /** 150 | * Merge provided config with existing config. Unlike `config()` which does 151 | * a recursive merge for nested keys, this method does a simple merge. 152 | * 153 | * Setting a specific value: 154 | * 155 | * ``` 156 | * $this->configShallow('key', $value); 157 | * ``` 158 | * 159 | * Setting a nested value: 160 | * 161 | * ``` 162 | * $this->configShallow('some.nested.key', $value); 163 | * ``` 164 | * 165 | * Updating multiple config settings at the same time: 166 | * 167 | * ``` 168 | * $this->configShallow(['one' => 'value', 'another' => 'value']); 169 | * ``` 170 | * 171 | * @param string|array $key The key to set, or a complete array of configs. 172 | * @param mixed|null $value The value to set. 173 | * @return $this 174 | */ 175 | public function configShallow($key, $value = null) 176 | { 177 | if (!$this->_configInitialized) { 178 | $this->_config = $this->_defaultConfig; 179 | $this->_configInitialized = true; 180 | } 181 | 182 | $this->_configWrite($key, $value, 'shallow'); 183 | 184 | return $this; 185 | } 186 | 187 | /** 188 | * Reads a config key. 189 | * 190 | * @param string|null $key Key to read. 191 | * @return mixed 192 | */ 193 | protected function _configRead(?string $key) 194 | { 195 | if ($key === null) { 196 | return $this->_config; 197 | } 198 | 199 | if (strpos($key, '.') === false) { 200 | return $this->_config[$key] ?? null; 201 | } 202 | 203 | $return = $this->_config; 204 | 205 | foreach (explode('.', $key) as $k) { 206 | if (!is_array($return) || !isset($return[$k])) { 207 | $return = null; 208 | break; 209 | } 210 | 211 | $return = $return[$k]; 212 | } 213 | 214 | return $return; 215 | } 216 | 217 | /** 218 | * Writes a config key. 219 | * 220 | * @param string|array $key Key to write to. 221 | * @param mixed $value Value to write. 222 | * @param bool|string $merge True to merge recursively, 'shallow' for simple merge, 223 | * false to overwrite, defaults to false. 224 | * @return void 225 | * @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config 226 | */ 227 | protected function _configWrite($key, $value, $merge = false): void 228 | { 229 | if (is_string($key) && $value === null) { 230 | $this->_configDelete($key); 231 | 232 | return; 233 | } 234 | 235 | if ($merge) { 236 | $update = is_array($key) ? $key : [$key => $value]; 237 | if ($merge === 'shallow') { 238 | $this->_config = array_merge($this->_config, Hash::expand($update)); 239 | } else { 240 | $this->_config = Hash::merge($this->_config, Hash::expand($update)); 241 | } 242 | 243 | return; 244 | } 245 | 246 | if (is_array($key)) { 247 | foreach ($key as $k => $val) { 248 | $this->_configWrite($k, $val); 249 | } 250 | 251 | return; 252 | } 253 | 254 | if (strpos($key, '.') === false) { 255 | $this->_config[$key] = $value; 256 | 257 | return; 258 | } 259 | 260 | $update = &$this->_config; 261 | $stack = explode('.', $key); 262 | 263 | foreach ($stack as $k) { 264 | if (!is_array($update)) { 265 | throw new CakeException(sprintf('Cannot set %s value', $key)); 266 | } 267 | 268 | if (!isset($update[$k])) { 269 | $update[$k] = []; 270 | } 271 | 272 | $update = &$update[$k]; 273 | } 274 | 275 | $update = $value; 276 | } 277 | 278 | /** 279 | * Deletes a single config key. 280 | * 281 | * @param string $key Key to delete. 282 | * @return void 283 | * @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config 284 | */ 285 | protected function _configDelete(string $key): void 286 | { 287 | if (strpos($key, '.') === false) { 288 | unset($this->_config[$key]); 289 | 290 | return; 291 | } 292 | 293 | $update = &$this->_config; 294 | $stack = explode('.', $key); 295 | $length = count($stack); 296 | 297 | foreach ($stack as $i => $k) { 298 | if (!is_array($update)) { 299 | throw new CakeException(sprintf('Cannot unset %s value', $key)); 300 | } 301 | 302 | if (!isset($update[$k])) { 303 | break; 304 | } 305 | 306 | if ($i === $length - 1) { 307 | unset($update[$k]); 308 | break; 309 | } 310 | 311 | $update = &$update[$k]; 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org) 4 | Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /ObjectRegistry.php: -------------------------------------------------------------------------------- 1 | 51 | */ 52 | protected $_loaded = []; 53 | 54 | /** 55 | * Loads/constructs an object instance. 56 | * 57 | * Will return the instance in the registry if it already exists. 58 | * If a subclass provides event support, you can use `$config['enabled'] = false` 59 | * to exclude constructed objects from being registered for events. 60 | * 61 | * Using Cake\Controller\Controller::$components as an example. You can alias 62 | * an object by setting the 'className' key, i.e., 63 | * 64 | * ``` 65 | * protected $components = [ 66 | * 'Email' => [ 67 | * 'className' => 'App\Controller\Component\AliasedEmailComponent' 68 | * ]; 69 | * ]; 70 | * ``` 71 | * 72 | * All calls to the `Email` component would use `AliasedEmail` instead. 73 | * 74 | * @param string $name The name/class of the object to load. 75 | * @param array $config Additional settings to use when loading the object. 76 | * @return mixed 77 | * @psalm-return TObject 78 | * @throws \Exception If the class cannot be found. 79 | */ 80 | public function load(string $name, array $config = []) 81 | { 82 | if (isset($config['className'])) { 83 | $objName = $name; 84 | $name = $config['className']; 85 | } else { 86 | [, $objName] = pluginSplit($name); 87 | } 88 | 89 | $loaded = isset($this->_loaded[$objName]); 90 | if ($loaded && !empty($config)) { 91 | $this->_checkDuplicate($objName, $config); 92 | } 93 | if ($loaded) { 94 | return $this->_loaded[$objName]; 95 | } 96 | 97 | $className = $name; 98 | if (is_string($name)) { 99 | $className = $this->_resolveClassName($name); 100 | if ($className === null) { 101 | [$plugin, $name] = pluginSplit($name); 102 | $this->_throwMissingClassError($name, $plugin); 103 | } 104 | } 105 | 106 | /** 107 | * @psalm-var TObject $instance 108 | * @psalm-suppress PossiblyNullArgument 109 | **/ 110 | $instance = $this->_create($className, $objName, $config); 111 | $this->_loaded[$objName] = $instance; 112 | 113 | return $instance; 114 | } 115 | 116 | /** 117 | * Check for duplicate object loading. 118 | * 119 | * If a duplicate is being loaded and has different configuration, that is 120 | * bad and an exception will be raised. 121 | * 122 | * An exception is raised, as replacing the object will not update any 123 | * references other objects may have. Additionally, simply updating the runtime 124 | * configuration is not a good option as we may be missing important constructor 125 | * logic dependent on the configuration. 126 | * 127 | * @param string $name The name of the alias in the registry. 128 | * @param array $config The config data for the new instance. 129 | * @return void 130 | * @throws \RuntimeException When a duplicate is found. 131 | */ 132 | protected function _checkDuplicate(string $name, array $config): void 133 | { 134 | $existing = $this->_loaded[$name]; 135 | $msg = sprintf('The "%s" alias has already been loaded.', $name); 136 | $hasConfig = method_exists($existing, 'getConfig'); 137 | if (!$hasConfig) { 138 | throw new RuntimeException($msg); 139 | } 140 | if (empty($config)) { 141 | return; 142 | } 143 | $existingConfig = $existing->getConfig(); 144 | unset($config['enabled'], $existingConfig['enabled']); 145 | 146 | $failure = null; 147 | foreach ($config as $key => $value) { 148 | if (!array_key_exists($key, $existingConfig)) { 149 | $failure = " The `{$key}` was not defined in the previous configuration data."; 150 | break; 151 | } 152 | if (isset($existingConfig[$key]) && $existingConfig[$key] !== $value) { 153 | $failure = sprintf( 154 | ' The `%s` key has a value of `%s` but previously had a value of `%s`', 155 | $key, 156 | json_encode($value), 157 | json_encode($existingConfig[$key]) 158 | ); 159 | break; 160 | } 161 | } 162 | if ($failure) { 163 | throw new RuntimeException($msg . $failure); 164 | } 165 | } 166 | 167 | /** 168 | * Should resolve the classname for a given object type. 169 | * 170 | * @param string $class The class to resolve. 171 | * @return string|null The resolved name or null for failure. 172 | * @psalm-return class-string|null 173 | */ 174 | abstract protected function _resolveClassName(string $class): ?string; 175 | 176 | /** 177 | * Throw an exception when the requested object name is missing. 178 | * 179 | * @param string $class The class that is missing. 180 | * @param string|null $plugin The plugin $class is missing from. 181 | * @return void 182 | * @throws \Exception 183 | */ 184 | abstract protected function _throwMissingClassError(string $class, ?string $plugin): void; 185 | 186 | /** 187 | * Create an instance of a given classname. 188 | * 189 | * This method should construct and do any other initialization logic 190 | * required. 191 | * 192 | * @param string|object $class The class to build. 193 | * @param string $alias The alias of the object. 194 | * @param array $config The Configuration settings for construction 195 | * @return object 196 | * @psalm-param string|TObject $class 197 | * @psalm-return TObject 198 | */ 199 | abstract protected function _create($class, string $alias, array $config); 200 | 201 | /** 202 | * Get the list of loaded objects. 203 | * 204 | * @return string[] List of object names. 205 | */ 206 | public function loaded(): array 207 | { 208 | return array_keys($this->_loaded); 209 | } 210 | 211 | /** 212 | * Check whether or not a given object is loaded. 213 | * 214 | * @param string $name The object name to check for. 215 | * @return bool True is object is loaded else false. 216 | */ 217 | public function has(string $name): bool 218 | { 219 | return isset($this->_loaded[$name]); 220 | } 221 | 222 | /** 223 | * Get loaded object instance. 224 | * 225 | * @param string $name Name of object. 226 | * @return object Object instance. 227 | * @throws \RuntimeException If not loaded or found. 228 | * @psalm-return TObject 229 | */ 230 | public function get(string $name) 231 | { 232 | if (!isset($this->_loaded[$name])) { 233 | throw new RuntimeException(sprintf('Unknown object "%s"', $name)); 234 | } 235 | 236 | return $this->_loaded[$name]; 237 | } 238 | 239 | /** 240 | * Provide public read access to the loaded objects 241 | * 242 | * @param string $name Name of property to read 243 | * @return object|null 244 | * @psalm-return TObject|null 245 | */ 246 | public function __get(string $name) 247 | { 248 | return $this->_loaded[$name] ?? null; 249 | } 250 | 251 | /** 252 | * Provide isset access to _loaded 253 | * 254 | * @param string $name Name of object being checked. 255 | * @return bool 256 | */ 257 | public function __isset(string $name): bool 258 | { 259 | return $this->has($name); 260 | } 261 | 262 | /** 263 | * Sets an object. 264 | * 265 | * @param string $name Name of a property to set. 266 | * @param object $object Object to set. 267 | * @psalm-param TObject $object 268 | * @return void 269 | */ 270 | public function __set(string $name, $object): void 271 | { 272 | $this->set($name, $object); 273 | } 274 | 275 | /** 276 | * Unsets an object. 277 | * 278 | * @param string $name Name of a property to unset. 279 | * @return void 280 | */ 281 | public function __unset(string $name): void 282 | { 283 | $this->unload($name); 284 | } 285 | 286 | /** 287 | * Normalizes an object array, creates an array that makes lazy loading 288 | * easier 289 | * 290 | * @param array $objects Array of child objects to normalize. 291 | * @return array[] Array of normalized objects. 292 | */ 293 | public function normalizeArray(array $objects): array 294 | { 295 | $normal = []; 296 | foreach ($objects as $i => $objectName) { 297 | $config = []; 298 | if (!is_int($i)) { 299 | $config = (array)$objectName; 300 | $objectName = $i; 301 | } 302 | [, $name] = pluginSplit($objectName); 303 | if (isset($config['class'])) { 304 | $normal[$name] = $config + ['config' => []]; 305 | } else { 306 | $normal[$name] = ['class' => $objectName, 'config' => $config]; 307 | } 308 | } 309 | 310 | return $normal; 311 | } 312 | 313 | /** 314 | * Clear loaded instances in the registry. 315 | * 316 | * If the registry subclass has an event manager, the objects will be detached from events as well. 317 | * 318 | * @return $this 319 | */ 320 | public function reset() 321 | { 322 | foreach (array_keys($this->_loaded) as $name) { 323 | $this->unload((string)$name); 324 | } 325 | 326 | return $this; 327 | } 328 | 329 | /** 330 | * Set an object directly into the registry by name. 331 | * 332 | * If this collection implements events, the passed object will 333 | * be attached into the event manager 334 | * 335 | * @param string $name The name of the object to set in the registry. 336 | * @param object $object instance to store in the registry 337 | * @return $this 338 | * @psalm-param TObject $object 339 | * @psalm-suppress MoreSpecificReturnType 340 | */ 341 | public function set(string $name, object $object) 342 | { 343 | [, $objName] = pluginSplit($name); 344 | 345 | // Just call unload if the object was loaded before 346 | if (array_key_exists($name, $this->_loaded)) { 347 | $this->unload($name); 348 | } 349 | if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) { 350 | $this->getEventManager()->on($object); 351 | } 352 | $this->_loaded[$objName] = $object; 353 | 354 | /** @psalm-suppress LessSpecificReturnStatement */ 355 | return $this; 356 | } 357 | 358 | /** 359 | * Remove an object from the registry. 360 | * 361 | * If this registry has an event manager, the object will be detached from any events as well. 362 | * 363 | * @param string $name The name of the object to remove from the registry. 364 | * @return $this 365 | * @psalm-suppress MoreSpecificReturnType 366 | */ 367 | public function unload(string $name) 368 | { 369 | if (empty($this->_loaded[$name])) { 370 | [$plugin, $name] = pluginSplit($name); 371 | $this->_throwMissingClassError($name, $plugin); 372 | } 373 | 374 | $object = $this->_loaded[$name]; 375 | if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) { 376 | $this->getEventManager()->off($object); 377 | } 378 | unset($this->_loaded[$name]); 379 | 380 | /** @psalm-suppress LessSpecificReturnStatement */ 381 | return $this; 382 | } 383 | 384 | /** 385 | * Returns an array iterator. 386 | * 387 | * @return \Traversable 388 | * @psalm-return \Traversable 389 | */ 390 | public function getIterator(): Traversable 391 | { 392 | return new ArrayIterator($this->_loaded); 393 | } 394 | 395 | /** 396 | * Returns the number of loaded objects. 397 | * 398 | * @return int 399 | */ 400 | public function count(): int 401 | { 402 | return count($this->_loaded); 403 | } 404 | 405 | /** 406 | * Debug friendly object properties. 407 | * 408 | * @return array 409 | */ 410 | public function __debugInfo(): array 411 | { 412 | $properties = get_object_vars($this); 413 | if (isset($properties['_loaded'])) { 414 | $properties['_loaded'] = array_keys($properties['_loaded']); 415 | } 416 | 417 | return $properties; 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | get($name); 46 | 47 | return $plugin->getPath(); 48 | } 49 | 50 | /** 51 | * Returns the filesystem path for plugin's folder containing class files. 52 | * 53 | * @param string $name name of the plugin in CamelCase format. 54 | * @return string Path to the plugin folder containing class files. 55 | * @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded. 56 | */ 57 | public static function classPath(string $name): string 58 | { 59 | $plugin = static::getCollection()->get($name); 60 | 61 | return $plugin->getClassPath(); 62 | } 63 | 64 | /** 65 | * Returns the filesystem path for plugin's folder containing config files. 66 | * 67 | * @param string $name name of the plugin in CamelCase format. 68 | * @return string Path to the plugin folder containing config files. 69 | * @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded. 70 | */ 71 | public static function configPath(string $name): string 72 | { 73 | $plugin = static::getCollection()->get($name); 74 | 75 | return $plugin->getConfigPath(); 76 | } 77 | 78 | /** 79 | * Returns the filesystem path for plugin's folder containing template files. 80 | * 81 | * @param string $name name of the plugin in CamelCase format. 82 | * @return string Path to the plugin folder containing template files. 83 | * @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded. 84 | */ 85 | public static function templatePath(string $name): string 86 | { 87 | $plugin = static::getCollection()->get($name); 88 | 89 | return $plugin->getTemplatePath(); 90 | } 91 | 92 | /** 93 | * Returns true if the plugin $plugin is already loaded. 94 | * 95 | * @param string $plugin Plugin name. 96 | * @return bool 97 | * @since 3.7.0 98 | */ 99 | public static function isLoaded(string $plugin): bool 100 | { 101 | return static::getCollection()->has($plugin); 102 | } 103 | 104 | /** 105 | * Return a list of loaded plugins. 106 | * 107 | * @return string[] A list of plugins that have been loaded 108 | */ 109 | public static function loaded(): array 110 | { 111 | $names = []; 112 | foreach (static::getCollection() as $plugin) { 113 | $names[] = $plugin->getName(); 114 | } 115 | sort($names); 116 | 117 | return $names; 118 | } 119 | 120 | /** 121 | * Get the shared plugin collection. 122 | * 123 | * This method should generally not be used during application 124 | * runtime as plugins should be set during Application startup. 125 | * 126 | * @return \Cake\Core\PluginCollection 127 | */ 128 | public static function getCollection(): PluginCollection 129 | { 130 | if (!isset(static::$plugins)) { 131 | static::$plugins = new PluginCollection(); 132 | } 133 | 134 | return static::$plugins; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /PluginApplicationInterface.php: -------------------------------------------------------------------------------- 1 | add($plugin); 77 | } 78 | $this->loadConfig(); 79 | } 80 | 81 | /** 82 | * Load the path information stored in vendor/cakephp-plugins.php 83 | * 84 | * This file is generated by the cakephp/plugin-installer package and used 85 | * to locate plugins on the filesystem as applications can use `extra.plugin-paths` 86 | * in their composer.json file to move plugin outside of vendor/ 87 | * 88 | * @internal 89 | * @return void 90 | */ 91 | protected function loadConfig(): void 92 | { 93 | if (Configure::check('plugins')) { 94 | return; 95 | } 96 | $vendorFile = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php'; 97 | if (!is_file($vendorFile)) { 98 | $vendorFile = dirname(dirname(dirname(dirname(__DIR__)))) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php'; 99 | if (!is_file($vendorFile)) { 100 | Configure::write(['plugins' => []]); 101 | 102 | return; 103 | } 104 | } 105 | 106 | $config = require $vendorFile; 107 | Configure::write($config); 108 | } 109 | 110 | /** 111 | * Locate a plugin path by looking at configuration data. 112 | * 113 | * This will use the `plugins` Configure key, and fallback to enumerating `App::path('plugins')` 114 | * 115 | * This method is not part of the official public API as plugins with 116 | * no plugin class are being phased out. 117 | * 118 | * @param string $name The plugin name to locate a path for. Will return '' when a plugin cannot be found. 119 | * @return string 120 | * @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved. 121 | * @internal 122 | */ 123 | public function findPath(string $name): string 124 | { 125 | // Ensure plugin config is loaded each time. This is necessary primarily 126 | // for testing because the Configure::clear() call in TestCase::tearDown() 127 | // wipes out all configuration including plugin paths config. 128 | $this->loadConfig(); 129 | 130 | $path = Configure::read('plugins.' . $name); 131 | if ($path) { 132 | return $path; 133 | } 134 | 135 | $pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $name); 136 | $paths = App::path('plugins'); 137 | foreach ($paths as $path) { 138 | if (is_dir($path . $pluginPath)) { 139 | return $path . $pluginPath . DIRECTORY_SEPARATOR; 140 | } 141 | } 142 | 143 | throw new MissingPluginException(['plugin' => $name]); 144 | } 145 | 146 | /** 147 | * Add a plugin to the collection 148 | * 149 | * Plugins will be keyed by their names. 150 | * 151 | * @param \Cake\Core\PluginInterface $plugin The plugin to load. 152 | * @return $this 153 | */ 154 | public function add(PluginInterface $plugin) 155 | { 156 | $name = $plugin->getName(); 157 | $this->plugins[$name] = $plugin; 158 | $this->names = array_keys($this->plugins); 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * Remove a plugin from the collection if it exists. 165 | * 166 | * @param string $name The named plugin. 167 | * @return $this 168 | */ 169 | public function remove(string $name) 170 | { 171 | unset($this->plugins[$name]); 172 | $this->names = array_keys($this->plugins); 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Remove all plugins from the collection 179 | * 180 | * @return $this 181 | */ 182 | public function clear() 183 | { 184 | $this->plugins = []; 185 | $this->names = []; 186 | $this->positions = []; 187 | $this->loopDepth = -1; 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * Check whether the named plugin exists in the collection. 194 | * 195 | * @param string $name The named plugin. 196 | * @return bool 197 | */ 198 | public function has(string $name): bool 199 | { 200 | return isset($this->plugins[$name]); 201 | } 202 | 203 | /** 204 | * Get the a plugin by name. 205 | * 206 | * If a plugin isn't already loaded it will be autoloaded on first access 207 | * and that plugins loaded this way may miss some hook methods. 208 | * 209 | * @param string $name The plugin to get. 210 | * @return \Cake\Core\PluginInterface The plugin. 211 | * @throws \Cake\Core\Exception\MissingPluginException when unknown plugins are fetched. 212 | */ 213 | public function get(string $name): PluginInterface 214 | { 215 | if ($this->has($name)) { 216 | return $this->plugins[$name]; 217 | } 218 | 219 | $plugin = $this->create($name); 220 | $this->add($plugin); 221 | 222 | return $plugin; 223 | } 224 | 225 | /** 226 | * Create a plugin instance from a name/classname and configuration. 227 | * 228 | * @param string $name The plugin name or classname 229 | * @param array $config Configuration options for the plugin. 230 | * @return \Cake\Core\PluginInterface 231 | * @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created. 232 | */ 233 | public function create(string $name, array $config = []): PluginInterface 234 | { 235 | if (strpos($name, '\\') !== false) { 236 | /** @var \Cake\Core\PluginInterface */ 237 | return new $name($config); 238 | } 239 | 240 | $config += ['name' => $name]; 241 | /** @var class-string<\Cake\Core\PluginInterface> $className */ 242 | $className = str_replace('/', '\\', $name) . '\\' . 'Plugin'; 243 | if (!class_exists($className)) { 244 | $className = BasePlugin::class; 245 | if (empty($config['path'])) { 246 | $config['path'] = $this->findPath($name); 247 | } 248 | } 249 | 250 | return new $className($config); 251 | } 252 | 253 | /** 254 | * Implementation of Countable. 255 | * 256 | * Get the number of plugins in the collection. 257 | * 258 | * @return int 259 | */ 260 | public function count(): int 261 | { 262 | return count($this->plugins); 263 | } 264 | 265 | /** 266 | * Part of Iterator Interface 267 | * 268 | * @return void 269 | */ 270 | public function next(): void 271 | { 272 | $this->positions[$this->loopDepth]++; 273 | } 274 | 275 | /** 276 | * Part of Iterator Interface 277 | * 278 | * @return string 279 | */ 280 | public function key(): string 281 | { 282 | return $this->names[$this->positions[$this->loopDepth]]; 283 | } 284 | 285 | /** 286 | * Part of Iterator Interface 287 | * 288 | * @return \Cake\Core\PluginInterface 289 | */ 290 | public function current(): PluginInterface 291 | { 292 | $position = $this->positions[$this->loopDepth]; 293 | $name = $this->names[$position]; 294 | 295 | return $this->plugins[$name]; 296 | } 297 | 298 | /** 299 | * Part of Iterator Interface 300 | * 301 | * @return void 302 | */ 303 | public function rewind(): void 304 | { 305 | $this->positions[] = 0; 306 | $this->loopDepth += 1; 307 | } 308 | 309 | /** 310 | * Part of Iterator Interface 311 | * 312 | * @return bool 313 | */ 314 | public function valid(): bool 315 | { 316 | $valid = isset($this->names[$this->positions[$this->loopDepth]]); 317 | if (!$valid) { 318 | array_pop($this->positions); 319 | $this->loopDepth -= 1; 320 | } 321 | 322 | return $valid; 323 | } 324 | 325 | /** 326 | * Filter the plugins to those with the named hook enabled. 327 | * 328 | * @param string $hook The hook to filter plugins by 329 | * @return \Generator&\Cake\Core\PluginInterface[] A generator containing matching plugins. 330 | * @throws \InvalidArgumentException on invalid hooks 331 | * @psalm-return \Generator<\Cake\Core\PluginInterface> 332 | */ 333 | public function with(string $hook): Generator 334 | { 335 | if (!in_array($hook, PluginInterface::VALID_HOOKS, true)) { 336 | throw new InvalidArgumentException("The `{$hook}` hook is not a known plugin hook."); 337 | } 338 | foreach ($this as $plugin) { 339 | if ($plugin->isEnabled($hook)) { 340 | yield $plugin; 341 | } 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /PluginInterface.php: -------------------------------------------------------------------------------- 1 | strategy = $strategy; 55 | $this->maxRetries = $maxRetries; 56 | } 57 | 58 | /** 59 | * The number of retries to perform in case of failure 60 | * 61 | * @param callable $action The callable action to execute with a retry strategy 62 | * @return mixed The return value of the passed action callable 63 | * @throws \Exception 64 | */ 65 | public function run(callable $action) 66 | { 67 | $this->numRetries = 0; 68 | while (true) { 69 | try { 70 | return $action(); 71 | } catch (Exception $e) { 72 | if ( 73 | $this->numRetries < $this->maxRetries && 74 | $this->strategy->shouldRetry($e, $this->numRetries) 75 | ) { 76 | $this->numRetries++; 77 | continue; 78 | } 79 | 80 | throw $e; 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Returns the last number of retry attemps. 87 | * 88 | * @return int 89 | */ 90 | public function getRetries(): int 91 | { 92 | return $this->numRetries; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Retry/RetryStrategyInterface.php: -------------------------------------------------------------------------------- 1 | bootstrap($this->getContainer()); 71 | } 72 | 73 | /** 74 | * Bootstrap hook for ServiceProviders 75 | * 76 | * This hook should be implemented if your service provider 77 | * needs to register additional service providers, load configuration 78 | * files or do any other work when the service provider is added to the 79 | * container. 80 | * 81 | * @param \Cake\Core\ContainerInterface $container The container to add services to. 82 | * @return void 83 | */ 84 | public function bootstrap(ContainerInterface $container): void 85 | { 86 | } 87 | 88 | /** 89 | * Call the abstract services() method. 90 | * 91 | * This method primarily exists as a shim between the interface 92 | * that league/container has and the one we want to offer in CakePHP. 93 | * 94 | * @return void 95 | */ 96 | public function register() 97 | { 98 | $this->services($this->getContainer()); 99 | } 100 | 101 | /** 102 | * Register the services in a provider. 103 | * 104 | * All services registered in this method should also be included in the $provides 105 | * property so that services can be located. 106 | * 107 | * @param \Cake\Core\ContainerInterface $container The container to add services to. 108 | * @return void 109 | */ 110 | abstract public function services(ContainerInterface $container): void; 111 | } 112 | -------------------------------------------------------------------------------- /StaticConfigTrait.php: -------------------------------------------------------------------------------- 1 | configuration data for adapter. 72 | * @throws \BadMethodCallException When trying to modify an existing config. 73 | * @throws \LogicException When trying to store an invalid structured config array. 74 | * @return void 75 | */ 76 | public static function setConfig($key, $config = null): void 77 | { 78 | if ($config === null) { 79 | if (!is_array($key)) { 80 | throw new LogicException('If config is null, key must be an array.'); 81 | } 82 | foreach ($key as $name => $settings) { 83 | static::setConfig($name, $settings); 84 | } 85 | 86 | return; 87 | } 88 | 89 | if (isset(static::$_config[$key])) { 90 | /** @psalm-suppress PossiblyInvalidArgument */ 91 | throw new BadMethodCallException(sprintf('Cannot reconfigure existing key "%s"', $key)); 92 | } 93 | 94 | if (is_object($config)) { 95 | $config = ['className' => $config]; 96 | } 97 | 98 | if (isset($config['url'])) { 99 | $parsed = static::parseDsn($config['url']); 100 | unset($config['url']); 101 | $config = $parsed + $config; 102 | } 103 | 104 | if (isset($config['engine']) && empty($config['className'])) { 105 | $config['className'] = $config['engine']; 106 | unset($config['engine']); 107 | } 108 | /** @psalm-suppress InvalidPropertyAssignmentValue */ 109 | static::$_config[$key] = $config; 110 | } 111 | 112 | /** 113 | * Reads existing configuration. 114 | * 115 | * @param string $key The name of the configuration. 116 | * @return mixed|null Configuration data at the named key or null if the key does not exist. 117 | */ 118 | public static function getConfig(string $key) 119 | { 120 | return static::$_config[$key] ?? null; 121 | } 122 | 123 | /** 124 | * Reads existing configuration for a specific key. 125 | * 126 | * The config value for this key must exist, it can never be null. 127 | * 128 | * @param string $key The name of the configuration. 129 | * @return mixed Configuration data at the named key. 130 | * @throws \InvalidArgumentException If value does not exist. 131 | */ 132 | public static function getConfigOrFail(string $key) 133 | { 134 | if (!isset(static::$_config[$key])) { 135 | throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key)); 136 | } 137 | 138 | return static::$_config[$key]; 139 | } 140 | 141 | /** 142 | * Drops a constructed adapter. 143 | * 144 | * If you wish to modify an existing configuration, you should drop it, 145 | * change configuration and then re-add it. 146 | * 147 | * If the implementing objects supports a `$_registry` object the named configuration 148 | * will also be unloaded from the registry. 149 | * 150 | * @param string $config An existing configuration you wish to remove. 151 | * @return bool Success of the removal, returns false when the config does not exist. 152 | */ 153 | public static function drop(string $config): bool 154 | { 155 | if (!isset(static::$_config[$config])) { 156 | return false; 157 | } 158 | /** @psalm-suppress RedundantPropertyInitializationCheck */ 159 | if (isset(static::$_registry)) { 160 | static::$_registry->unload($config); 161 | } 162 | unset(static::$_config[$config]); 163 | 164 | return true; 165 | } 166 | 167 | /** 168 | * Returns an array containing the named configurations 169 | * 170 | * @return string[] Array of configurations. 171 | */ 172 | public static function configured(): array 173 | { 174 | $configurations = array_keys(static::$_config); 175 | 176 | return array_map(function ($key) { 177 | return (string)$key; 178 | }, $configurations); 179 | } 180 | 181 | /** 182 | * Parses a DSN into a valid connection configuration 183 | * 184 | * This method allows setting a DSN using formatting similar to that used by PEAR::DB. 185 | * The following is an example of its usage: 186 | * 187 | * ``` 188 | * $dsn = 'mysql://user:pass@localhost/database?'; 189 | * $config = ConnectionManager::parseDsn($dsn); 190 | * 191 | * $dsn = 'Cake\Log\Engine\FileLog://?types=notice,info,debug&file=debug&path=LOGS'; 192 | * $config = Log::parseDsn($dsn); 193 | * 194 | * $dsn = 'smtp://user:secret@localhost:25?timeout=30&client=null&tls=null'; 195 | * $config = Email::parseDsn($dsn); 196 | * 197 | * $dsn = 'file:///?className=\My\Cache\Engine\FileEngine'; 198 | * $config = Cache::parseDsn($dsn); 199 | * 200 | * $dsn = 'File://?prefix=myapp_cake_core_&serialize=true&duration=+2 minutes&path=/tmp/persistent/'; 201 | * $config = Cache::parseDsn($dsn); 202 | * ``` 203 | * 204 | * For all classes, the value of `scheme` is set as the value of both the `className` 205 | * unless they have been otherwise specified. 206 | * 207 | * Note that querystring arguments are also parsed and set as values in the returned configuration. 208 | * 209 | * @param string $dsn The DSN string to convert to a configuration array 210 | * @return array The configuration array to be stored after parsing the DSN 211 | * @throws \InvalidArgumentException If not passed a string, or passed an invalid string 212 | */ 213 | public static function parseDsn(string $dsn): array 214 | { 215 | if (empty($dsn)) { 216 | return []; 217 | } 218 | 219 | $pattern = <<<'REGEXP' 220 | { 221 | ^ 222 | (?P<_scheme> 223 | (?P[\w\\\\]+):// 224 | ) 225 | (?P<_username> 226 | (?P.*?) 227 | (?P<_password> 228 | :(?P.*?) 229 | )? 230 | @ 231 | )? 232 | (?P<_host> 233 | (?P[^?#/:@]+) 234 | (?P<_port> 235 | :(?P\d+) 236 | )? 237 | )? 238 | (?P<_path> 239 | (?P/[^?#]*) 240 | )? 241 | (?P<_query> 242 | \?(?P[^#]*) 243 | )? 244 | (?P<_fragment> 245 | \#(?P.*) 246 | )? 247 | $ 248 | }x 249 | REGEXP; 250 | 251 | preg_match($pattern, $dsn, $parsed); 252 | 253 | if (!$parsed) { 254 | throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed."); 255 | } 256 | 257 | $exists = []; 258 | foreach ($parsed as $k => $v) { 259 | if (is_int($k)) { 260 | unset($parsed[$k]); 261 | } elseif (strpos($k, '_') === 0) { 262 | $exists[substr($k, 1)] = ($v !== ''); 263 | unset($parsed[$k]); 264 | } elseif ($v === '' && !$exists[$k]) { 265 | unset($parsed[$k]); 266 | } 267 | } 268 | 269 | $query = ''; 270 | 271 | if (isset($parsed['query'])) { 272 | $query = $parsed['query']; 273 | unset($parsed['query']); 274 | } 275 | 276 | parse_str($query, $queryArgs); 277 | 278 | foreach ($queryArgs as $key => $value) { 279 | if ($value === 'true') { 280 | $queryArgs[$key] = true; 281 | } elseif ($value === 'false') { 282 | $queryArgs[$key] = false; 283 | } elseif ($value === 'null') { 284 | $queryArgs[$key] = null; 285 | } 286 | } 287 | 288 | $parsed = $queryArgs + $parsed; 289 | 290 | if (empty($parsed['className'])) { 291 | $classMap = static::getDsnClassMap(); 292 | 293 | $parsed['className'] = $parsed['scheme']; 294 | if (isset($classMap[$parsed['scheme']])) { 295 | /** @psalm-suppress PossiblyNullArrayOffset */ 296 | $parsed['className'] = $classMap[$parsed['scheme']]; 297 | } 298 | } 299 | 300 | return $parsed; 301 | } 302 | 303 | /** 304 | * Updates the DSN class map for this class. 305 | * 306 | * @param string[] $map Additions/edits to the class map to apply. 307 | * @return void 308 | * @psalm-param array $map 309 | */ 310 | public static function setDsnClassMap(array $map): void 311 | { 312 | static::$_dsnClassMap = $map + static::$_dsnClassMap; 313 | } 314 | 315 | /** 316 | * Returns the DSN class map for this class. 317 | * 318 | * @return string[] 319 | * @psalm-return array 320 | */ 321 | public static function getDsnClassMap(): array 322 | { 323 | return static::$_dsnClassMap; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cakephp/core", 3 | "description": "CakePHP Framework Core classes", 4 | "type": "library", 5 | "keywords": [ 6 | "cakephp", 7 | "framework", 8 | "core" 9 | ], 10 | "homepage": "https://cakephp.org", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "CakePHP Community", 15 | "homepage": "https://github.com/cakephp/core/graphs/contributors" 16 | } 17 | ], 18 | "support": { 19 | "issues": "https://github.com/cakephp/cakephp/issues", 20 | "forum": "https://stackoverflow.com/tags/cakephp", 21 | "irc": "irc://irc.freenode.org/cakephp", 22 | "source": "https://github.com/cakephp/core" 23 | }, 24 | "require": { 25 | "php": ">=7.2.0", 26 | "cakephp/utility": "^4.0" 27 | }, 28 | "suggest": { 29 | "cakephp/event": "To use PluginApplicationInterface or plugin applications.", 30 | "cakephp/cache": "To use Configure::store() and restore().", 31 | "league/container": "To use Container and ServiceProvider classes" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Cake\\Core\\": "." 36 | }, 37 | "files": [ 38 | "functions.php" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | $t) { 48 | $texts[$k] = h($t, $double, $charset); 49 | } 50 | 51 | return $texts; 52 | } elseif (is_object($text)) { 53 | if (method_exists($text, '__toString')) { 54 | $text = $text->__toString(); 55 | } else { 56 | $text = '(object)' . get_class($text); 57 | } 58 | } elseif ($text === null || is_scalar($text)) { 59 | return $text; 60 | } 61 | 62 | static $defaultCharset = false; 63 | if ($defaultCharset === false) { 64 | $defaultCharset = mb_internal_encoding() ?: 'UTF-8'; 65 | } 66 | 67 | return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset ?: $defaultCharset, $double); 68 | } 69 | 70 | } 71 | 72 | if (!function_exists('pluginSplit')) { 73 | /** 74 | * Splits a dot syntax plugin name into its plugin and class name. 75 | * If $name does not have a dot, then index 0 will be null. 76 | * 77 | * Commonly used like 78 | * ``` 79 | * list($plugin, $name) = pluginSplit($name); 80 | * ``` 81 | * 82 | * @param string $name The name you want to plugin split. 83 | * @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it. 84 | * @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null. 85 | * @return array Array with 2 indexes. 0 => plugin name, 1 => class name. 86 | * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit 87 | * @psalm-return array{string|null, string} 88 | */ 89 | function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array 90 | { 91 | if (strpos($name, '.') !== false) { 92 | $parts = explode('.', $name, 2); 93 | if ($dotAppend) { 94 | $parts[0] .= '.'; 95 | } 96 | 97 | /** @psalm-var array{string, string}*/ 98 | return $parts; 99 | } 100 | 101 | return [$plugin, $name]; 102 | } 103 | 104 | } 105 | 106 | if (!function_exists('namespaceSplit')) { 107 | /** 108 | * Split the namespace from the classname. 109 | * 110 | * Commonly used like `list($namespace, $className) = namespaceSplit($class);`. 111 | * 112 | * @param string $class The full class name, ie `Cake\Core\App`. 113 | * @return string[] Array with 2 indexes. 0 => namespace, 1 => classname. 114 | */ 115 | function namespaceSplit(string $class): array 116 | { 117 | $pos = strrpos($class, '\\'); 118 | if ($pos === false) { 119 | return ['', $class]; 120 | } 121 | 122 | return [substr($class, 0, $pos), substr($class, $pos + 1)]; 123 | } 124 | 125 | } 126 | 127 | if (!function_exists('pr')) { 128 | /** 129 | * print_r() convenience function. 130 | * 131 | * In terminals this will act similar to using print_r() directly, when not run on CLI 132 | * print_r() will also wrap `
` tags around the output of given variable. Similar to debug().
133 |      *
134 |      * This function returns the same variable that was passed.
135 |      *
136 |      * @param mixed $var Variable to print out.
137 |      * @return mixed the same $var that was passed to this function
138 |      * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr
139 |      * @see debug()
140 |      */
141 |     function pr($var)
142 |     {
143 |         if (!Configure::read('debug')) {
144 |             return $var;
145 |         }
146 | 
147 |         $template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '
%s
' : "\n%s\n\n"; 148 | printf($template, trim(print_r($var, true))); 149 | 150 | return $var; 151 | } 152 | 153 | } 154 | 155 | if (!function_exists('pj')) { 156 | /** 157 | * JSON pretty print convenience function. 158 | * 159 | * In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI 160 | * will also wrap `
` tags around the output of given variable. Similar to pr().
161 |      *
162 |      * This function returns the same variable that was passed.
163 |      *
164 |      * @param mixed $var Variable to print out.
165 |      * @return mixed the same $var that was passed to this function
166 |      * @see pr()
167 |      * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj
168 |      */
169 |     function pj($var)
170 |     {
171 |         if (!Configure::read('debug')) {
172 |             return $var;
173 |         }
174 | 
175 |         $template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '
%s
' : "\n%s\n\n"; 176 | printf($template, trim(json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))); 177 | 178 | return $var; 179 | } 180 | 181 | } 182 | 183 | if (!function_exists('env')) { 184 | /** 185 | * Gets an environment variable from available sources, and provides emulation 186 | * for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on 187 | * IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom 188 | * environment information. 189 | * 190 | * @param string $key Environment variable name. 191 | * @param string|bool|null $default Specify a default value in case the environment variable is not defined. 192 | * @return string|bool|null Environment variable setting. 193 | * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env 194 | */ 195 | function env(string $key, $default = null) 196 | { 197 | if ($key === 'HTTPS') { 198 | if (isset($_SERVER['HTTPS'])) { 199 | return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; 200 | } 201 | 202 | return strpos((string)env('SCRIPT_URI'), 'https://') === 0; 203 | } 204 | 205 | if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) { 206 | $key = 'SCRIPT_URL'; 207 | } 208 | 209 | $val = null; 210 | if (isset($_SERVER[$key])) { 211 | $val = $_SERVER[$key]; 212 | } elseif (isset($_ENV[$key])) { 213 | $val = $_ENV[$key]; 214 | } elseif (getenv($key) !== false) { 215 | $val = getenv($key); 216 | } 217 | 218 | if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) { 219 | $addr = env('HTTP_PC_REMOTE_ADDR'); 220 | if ($addr !== null) { 221 | $val = $addr; 222 | } 223 | } 224 | 225 | if ($val !== null) { 226 | return $val; 227 | } 228 | 229 | switch ($key) { 230 | case 'DOCUMENT_ROOT': 231 | $name = (string)env('SCRIPT_NAME'); 232 | $filename = (string)env('SCRIPT_FILENAME'); 233 | $offset = 0; 234 | if (!strpos($name, '.php')) { 235 | $offset = 4; 236 | } 237 | 238 | return substr($filename, 0, -(strlen($name) + $offset)); 239 | case 'PHP_SELF': 240 | return str_replace((string)env('DOCUMENT_ROOT'), '', (string)env('SCRIPT_FILENAME')); 241 | case 'CGI_MODE': 242 | return PHP_SAPI === 'cgi'; 243 | } 244 | 245 | return $default; 246 | } 247 | 248 | } 249 | 250 | if (!function_exists('triggerWarning')) { 251 | /** 252 | * Triggers an E_USER_WARNING. 253 | * 254 | * @param string $message The warning message. 255 | * @return void 256 | */ 257 | function triggerWarning(string $message): void 258 | { 259 | $stackFrame = 1; 260 | $trace = debug_backtrace(); 261 | if (isset($trace[$stackFrame])) { 262 | $frame = $trace[$stackFrame]; 263 | $frame += ['file' => '[internal]', 'line' => '??']; 264 | $message = sprintf( 265 | '%s - %s, line: %s', 266 | $message, 267 | $frame['file'], 268 | $frame['line'] 269 | ); 270 | } 271 | trigger_error($message, E_USER_WARNING); 272 | } 273 | } 274 | 275 | if (!function_exists('deprecationWarning')) { 276 | /** 277 | * Helper method for outputting deprecation warnings 278 | * 279 | * @param string $message The message to output as a deprecation warning. 280 | * @param int $stackFrame The stack frame to include in the error. Defaults to 1 281 | * as that should point to application/plugin code. 282 | * @return void 283 | */ 284 | function deprecationWarning(string $message, int $stackFrame = 1): void 285 | { 286 | if (!(error_reporting() & E_USER_DEPRECATED)) { 287 | return; 288 | } 289 | 290 | $trace = debug_backtrace(); 291 | if (isset($trace[$stackFrame])) { 292 | $frame = $trace[$stackFrame]; 293 | $frame += ['file' => '[internal]', 'line' => '??']; 294 | 295 | $relative = str_replace(DIRECTORY_SEPARATOR, '/', substr($frame['file'], strlen(ROOT) + 1)); 296 | $patterns = (array)Configure::read('Error.ignoredDeprecationPaths'); 297 | foreach ($patterns as $pattern) { 298 | $pattern = str_replace(DIRECTORY_SEPARATOR, '/', $pattern); 299 | if (fnmatch($pattern, $relative)) { 300 | return; 301 | } 302 | } 303 | 304 | $message = sprintf( 305 | '%s - %s, line: %s' . "\n" . 306 | ' You can disable all deprecation warnings by setting `Error.errorLevel` to' . 307 | ' `E_ALL & ~E_USER_DEPRECATED`, or add `%s` to ' . 308 | ' `Error.ignoredDeprecationPaths` in your `config/app.php` to mute deprecations from only this file.', 309 | $message, 310 | $frame['file'], 311 | $frame['line'], 312 | $relative 313 | ); 314 | } 315 | 316 | trigger_error($message, E_USER_DEPRECATED); 317 | } 318 | } 319 | 320 | if (!function_exists('getTypeName')) { 321 | /** 322 | * Returns the objects class or var type of it's not an object 323 | * 324 | * @param mixed $var Variable to check 325 | * @return string Returns the class name or variable type 326 | */ 327 | function getTypeName($var): string 328 | { 329 | return is_object($var) ? get_class($var) : gettype($var); 330 | } 331 | } 332 | --------------------------------------------------------------------------------