Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [Factories] Config caching #7696

Merged
merged 17 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.13.1@086b94371304750d1c673315321a55d15fc59015">
<file src="system/Cache/FactoriesCache/FileVarExportHandler.php">
<UndefinedVariable>
<code>$val</code>
</UndefinedVariable>
</file>
<file src="system/Cache/Handlers/MemcachedHandler.php">
<UndefinedClass>
<code>Memcache</code>
Expand Down
14 changes: 14 additions & 0 deletions public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();

// Define ENVIRONMENT
if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
}

MGatner marked this conversation as resolved.
Show resolved Hide resolved
// Load Config Cache
// $factoriesCache = new \CodeIgniter\Cache\FactoriesCache();
// $factoriesCache->load('config');
// ^^^ Uncomment these lines if you want to use Config Caching.

/*
* ---------------------------------------------------------------
* GRAB OUR CODEIGNITER INSTANCE
Expand All @@ -68,6 +78,10 @@

$app->run();

// Save Config Cache
// $factoriesCache->save('config');
samsonasik marked this conversation as resolved.
Show resolved Hide resolved
// ^^^ Uncomment this line if you want to use Config Caching.

// Exits the application, setting the exit code for CLI-based applications
// that might be watching.
exit(EXIT_SUCCESS);
5 changes: 5 additions & 0 deletions spark
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstra
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();

// Define ENVIRONMENT
if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
}

// Grab our CodeIgniter
$app = Config\Services::codeigniter();
$app->initialize();
Expand Down
65 changes: 65 additions & 0 deletions system/Cache/FactoriesCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Cache;

use CodeIgniter\Cache\FactoriesCache\FileVarExportHandler;
use CodeIgniter\Config\Factories;

final class FactoriesCache
{
/**
* @var CacheInterface|FileVarExportHandler
*/
private $cache;

/**
* @param CacheInterface|FileVarExportHandler|null $cache
*/
public function __construct($cache = null)
{
$this->cache = $cache ?? new FileVarExportHandler();
}

public function save(string $component): void
{
if (! Factories::isUpdated($component)) {
return;
}

$data = Factories::getComponentInstances($component);

$this->cache->save($this->getCacheKey($component), $data, 3600 * 24);
}

private function getCacheKey(string $component): string
{
return 'FactoriesCache_' . $component;
}

public function load(string $component): bool
{
$key = $this->getCacheKey($component);

if (! $data = $this->cache->get($key)) {
return false;
}

Factories::setComponentInstances($component, $data);

return true;
}

public function delete(string $component): void
{
$this->cache->delete($this->getCacheKey($component));
}
}
44 changes: 44 additions & 0 deletions system/Cache/FactoriesCache/FileVarExportHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Cache\FactoriesCache;

final class FileVarExportHandler
{
private string $path = WRITEPATH . 'cache';

/**
* @param array|bool|float|int|object|string|null $val
*/
public function save(string $key, $val): void
{
$val = var_export($val, true);

// Write to temp file first to ensure atomicity
$tmp = $this->path . "/{$key}." . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php return ' . $val . ';', LOCK_EX);

rename($tmp, $this->path . "/{$key}");
}

public function delete(string $key): void
{
@unlink($this->path . "/{$key}");
}

/**
* @return array|bool|float|int|object|string|null
*/
public function get(string $key)
{
return @include $this->path . "/{$key}";
}
}
3 changes: 2 additions & 1 deletion system/CodeIgniter.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ public function __construct(App $config)
public function initialize()
{
// Define environment variables
$this->detectEnvironment();
$this->bootstrapEnvironment();

// Setup Exception Handling
Expand Down Expand Up @@ -560,6 +559,8 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
* production
*
* @codeCoverageIgnore
*
* @deprecated 4.4.0 No longer used. Moved to index.php and spark.
*/
protected function detectEnvironment()
{
Expand Down
58 changes: 41 additions & 17 deletions system/Config/BaseConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* from the environment.
*
* These can be set within the .env file.
*
* @phpstan-consistent-constructor
*/
class BaseConfig
{
Expand All @@ -37,6 +39,11 @@ class BaseConfig
*/
public static $registrars = [];

/**
* Whether to override properties by Env vars and Registrars.
*/
public static bool $override = true;

/**
* Has module discovery happened yet?
*
Expand All @@ -51,6 +58,21 @@ class BaseConfig
*/
protected static $moduleConfig;

public static function __set_state(array $array)
{
static::$override = false;
$obj = new static();
static::$override = true;

$properties = array_keys(get_object_vars($obj));

foreach ($properties as $property) {
$obj->{$property} = $array[$property];
}

return $obj;
}

/**
* Will attempt to get environment variables with names
* that match the properties of the child class.
Expand All @@ -61,23 +83,25 @@ public function __construct()
{
static::$moduleConfig = config(Modules::class);

$this->registerProperties();

$properties = array_keys(get_object_vars($this));
$prefix = static::class;
$slashAt = strrpos($prefix, '\\');
$shortPrefix = strtolower(substr($prefix, $slashAt === false ? 0 : $slashAt + 1));

foreach ($properties as $property) {
$this->initEnvValue($this->{$property}, $property, $prefix, $shortPrefix);

if ($this instanceof Encryption && $property === 'key') {
if (strpos($this->{$property}, 'hex2bin:') === 0) {
// Handle hex2bin prefix
$this->{$property} = hex2bin(substr($this->{$property}, 8));
} elseif (strpos($this->{$property}, 'base64:') === 0) {
// Handle base64 prefix
$this->{$property} = base64_decode(substr($this->{$property}, 7), true);
if (static::$override) {
samsonasik marked this conversation as resolved.
Show resolved Hide resolved
$this->registerProperties();

$properties = array_keys(get_object_vars($this));
$prefix = static::class;
$slashAt = strrpos($prefix, '\\');
$shortPrefix = strtolower(substr($prefix, $slashAt === false ? 0 : $slashAt + 1));

foreach ($properties as $property) {
$this->initEnvValue($this->{$property}, $property, $prefix, $shortPrefix);

if ($this instanceof Encryption && $property === 'key') {
if (strpos($this->{$property}, 'hex2bin:') === 0) {
// Handle hex2bin prefix
$this->{$property} = hex2bin(substr($this->{$property}, 8));
} elseif (strpos($this->{$property}, 'base64:') === 0) {
// Handle base64 prefix
$this->{$property} = base64_decode(substr($this->{$property}, 7), true);
}
}
}
}
Expand Down
56 changes: 55 additions & 1 deletion system/Config/Factories.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ class Factories
*/
protected static $instances = [];

/**
* Whether the component instances are updated?
*
* @var array<string, true> [component => true]
*
* @internal For caching only
*/
protected static $updated = [];

/**
* Define the class to load. You can *override* the concrete class.
*
Expand Down Expand Up @@ -153,6 +162,7 @@ public static function __callStatic(string $component, array $arguments)

self::$instances[$options['component']][$class] = new $class(...$arguments);
self::$aliases[$options['component']][$alias] = $class;
self::$updated[$options['component']] = true;

// If a short classname is specified, also register FQCN to share the instance.
if (! isset(self::$aliases[$options['component']][$class])) {
Expand Down Expand Up @@ -383,7 +393,8 @@ public static function reset(?string $component = null)
unset(
static::$options[$component],
static::$aliases[$component],
static::$instances[$component]
static::$instances[$component],
static::$updated[$component]
);

return;
Expand All @@ -392,6 +403,7 @@ public static function reset(?string $component = null)
static::$options = [];
static::$aliases = [];
static::$instances = [];
static::$updated = [];
}

/**
Expand Down Expand Up @@ -440,4 +452,46 @@ public static function getBasename(string $alias): string

return $alias;
}

/**
* Gets component data for caching.
*
* @internal For caching only
*/
public static function getComponentInstances(string $component): array
{
if (! isset(static::$aliases[$component])) {
return [
'aliases' => [],
'instances' => [],
];
}

return [
'aliases' => static::$aliases[$component],
'instances' => self::$instances[$component],
];
}

/**
* Sets component data
*
* @internal For caching only
*/
public static function setComponentInstances(string $component, array $data): void
{
static::$aliases[$component] = $data['aliases'];
self::$instances[$component] = $data['instances'];
unset(self::$updated[$component]);
}

/**
* Whether the component instances are updated?
*
* @internal For caching only
*/
public static function isUpdated(string $component): bool
{
return isset(self::$updated[$component]);
}
}
2 changes: 1 addition & 1 deletion system/Config/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static function cache(?Cache $config = null, bool $getShared = true)
return static::getSharedInstance('cache', $config);
}

$config ??= new Cache();
$config ??= config(Cache::class);

return CacheFactory::getHandler($config);
}
Expand Down
2 changes: 1 addition & 1 deletion system/HTTP/UserAgent.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class UserAgent
*/
public function __construct(?UserAgents $config = null)
{
$this->config = $config ?? new UserAgents();
$this->config = $config ?? config(UserAgents::class);

if (isset($_SERVER['HTTP_USER_AGENT'])) {
$this->agent = trim($_SERVER['HTTP_USER_AGENT']);
Expand Down
Loading
Loading