diff --git a/src/System/Integrate/Application.php b/src/System/Integrate/Application.php index f9b4d4f8..2ebf58b6 100644 --- a/src/System/Integrate/Application.php +++ b/src/System/Integrate/Application.php @@ -205,14 +205,12 @@ public function __construct(string $base_path) { parent::__construct(); - // base binding - static::$app = $this; - $this->set('app', $this); - $this->set(Application::class, $this); - $this->set(Container::class, $this); + // set base path + $this->setBasePath($base_path); + $this->setConfigPath($_ENV['CONFIG_PATH'] ?? DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR); - // load config and load provider - $this->loadConfig($base_path); + // base binding + $this->setBaseBinding(); // register base provider $this->register(IntegrateServiceProvider::class); @@ -231,43 +229,29 @@ public static function getIntance() return static::$app; } + /** + * Register base binding container. + */ + protected function setBaseBinding(): void + { + static::$app = $this; + $this->set('app', $this); + $this->set(Application::class, $this); + $this->set(Container::class, $this); + } + /** * Load and set Configuration to application. - * - * @param string $base_path Base path - * - * @return void */ - public function loadConfig(string $base_path) + public function loadConfig(ConfigRepository $configs): void { - // set base path - $this->setBasePath($base_path); - $this->setAppPath($base_path); - $config_path = $base_path . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR; - - // check file exis - $configs = $this->defaultConfigs(); - $paths = [ - 'app.config.php', - 'database.config.php', - 'pusher.config.php', - 'cachedriver.config.php', - 'view.config.php', - ]; - foreach ($paths as $path) { - $file_path = $config_path . $path; - - if (file_exists($file_path)) { - $config = include $file_path; - foreach ($config as $key => $value) { - $configs[$key] = $value; - } - } - } + // give access to get config directly + $this->set('config', fn (): ConfigRepository => $configs); // base env $this->set('environment', $configs['ENVIRONMENT']); // application path + $this->setAppPath($this->basePath()); $this->setModelPath($configs['MODEL_PATH']); $this->setViewPath($configs['VIEW_PATH']); $this->setViewPaths($configs['VIEW_PATHS']); @@ -277,7 +261,6 @@ public function loadConfig(string $base_path) $this->setCommandPath($configs['COMMAND_PATH']); $this->setCachePath($configs['CACHE_PATH']); $this->setCompiledViewPath($configs['COMPILED_VIEW_PATH']); - $this->setConfigPath($configs['CONFIG']); $this->setMiddlewarePath($configs['MIDDLEWARE']); $this->setProviderPath($configs['SERVICE_PROVIDER']); $this->setMigrationPath($configs['MIGRATION_PATH']); @@ -292,9 +275,7 @@ public function loadConfig(string $base_path) $this->set('config.view.extensions', $configs['VIEW_EXTENSIONS']); // load provider $this->providers = $configs['PROVIDERS']; - $this->defineder($configs); - // give access to get config directly - $this->set('config', $configs); + $this->defineder($configs->toArray()); } /** @@ -302,7 +283,7 @@ public function loadConfig(string $base_path) * * @return array Configs */ - private function defaultConfigs() + public function defaultConfigs() { return [ // app config @@ -660,6 +641,17 @@ public function appPath() return $this->get('path.app'); } + /** + * Get application (bootstrapper) cach path. + * default './boostrap/cache/'. + * + * @return string + */ + public function getApplicationCachePath() + { + return $this->basePath() . 'bootsrap' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR; + } + /** * Get model path. * @@ -1086,6 +1078,7 @@ protected function registerAlias(): void 'request' => [Request::class], 'view.instance' => [Templator::class], 'vite.gets' => [Vite::class], + 'config' => [ConfigRepository::class], ] as $abstrack => $aliases) { foreach ($aliases as $alias) { $this->alias($abstrack, $alias); diff --git a/src/System/Integrate/Bootstrap/ConfigProviders.php b/src/System/Integrate/Bootstrap/ConfigProviders.php new file mode 100644 index 00000000..e3be35ff --- /dev/null +++ b/src/System/Integrate/Bootstrap/ConfigProviders.php @@ -0,0 +1,32 @@ +configPath(); + $config = $app->defaultConfigs(); + $has_cache = false; + if (file_exists($file = $app->getApplicationCachePath() . 'config.php')) { + $config = array_merge($config, require $file); + $has_cache = true; + } + + if (false === $has_cache) { + foreach (glob("{$config_path}*.config.php") as $path) { + foreach (include $path as $key => $value) { + $config[$key] = $value; + } + } + } + + $app->loadConfig(new ConfigRepository($config)); + } +} diff --git a/src/System/Integrate/ConfigRepository.php b/src/System/Integrate/ConfigRepository.php new file mode 100644 index 00000000..8479c59c --- /dev/null +++ b/src/System/Integrate/ConfigRepository.php @@ -0,0 +1,88 @@ + + */ +class ConfigRepository implements \ArrayAccess +{ + /** + * Create new config using array. + * + * @param array $config + */ + public function __construct(protected $config = []) + { + } + + /** + * Checks if the given key or index exists in the config. + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->config); + } + + /** + * Get config. + */ + public function get(string $key, mixed $default = null): mixed + { + return $this->config[$key] ?? $default; + } + + /** + * Set new or create config. + */ + public function set(string $key, mixed $value): void + { + $this->config[$key] = $value; + } + + /** + * Convert back to array. + * + * @return array + */ + public function toArray(): array + { + return $this->config; + } + + // array access + + /** + * Checks if the given key or index exists in the config. + */ + public function offsetExists(mixed $offset): bool + { + return $this->has($offset); + } + + /** + * Get config. + */ + public function offsetGet(mixed $offset): mixed + { + return $this->get($offset); + } + + /** + * Set new or create config. + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->set($offset, $value); + } + + /** + * Unset or set to null. + */ + public function offsetUnset(mixed $offset): void + { + $this->set($offset, null); + } +} diff --git a/src/System/Integrate/Console/Karnel.php b/src/System/Integrate/Console/Karnel.php index 3dfcda85..7d5cd3e9 100644 --- a/src/System/Integrate/Console/Karnel.php +++ b/src/System/Integrate/Console/Karnel.php @@ -7,6 +7,7 @@ use System\Console\Style\Style; use System\Integrate\Application; use System\Integrate\Bootstrap\BootProviders; +use System\Integrate\Bootstrap\ConfigProviders; use System\Integrate\Bootstrap\RegisterProviders; class Karnel @@ -21,6 +22,7 @@ class Karnel /** @var array Apllication bootstrap register. */ protected array $bootstrappers = [ + ConfigProviders::class, RegisterProviders::class, BootProviders::class, ]; diff --git a/src/System/Integrate/Http/Karnel.php b/src/System/Integrate/Http/Karnel.php index f3818ba9..6d7e60bf 100644 --- a/src/System/Integrate/Http/Karnel.php +++ b/src/System/Integrate/Http/Karnel.php @@ -8,6 +8,7 @@ use System\Http\Response; use System\Integrate\Application; use System\Integrate\Bootstrap\BootProviders; +use System\Integrate\Bootstrap\ConfigProviders; use System\Integrate\Bootstrap\RegisterProviders; use System\Integrate\Exceptions\Handler; use System\Integrate\Http\Middleware\MaintenanceMiddleware; @@ -30,6 +31,7 @@ class Karnel /** @var array Apllication bootstrap register. */ protected array $bootstrappers = [ + ConfigProviders::class, RegisterProviders::class, BootProviders::class, ]; diff --git a/tests/Integrate/ApplicationTest.php b/tests/Integrate/ApplicationTest.php index 4e68739e..28befb37 100644 --- a/tests/Integrate/ApplicationTest.php +++ b/tests/Integrate/ApplicationTest.php @@ -3,6 +3,7 @@ use PHPUnit\Framework\TestCase; use System\Http\Request; use System\Integrate\Application; +use System\Integrate\ConfigRepository; use System\Integrate\Exceptions\ApplicationNotAvailable; use System\Integrate\Http\Exception\HttpException; @@ -38,13 +39,15 @@ public function itCanLoadApp() } /** @test */ - public function itCanLoadDefaultConfig() + public function itCanLoadConfigFromDefault() { $app = new Application('/'); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); + /** @var Config */ $config = $app->get('config'); - $this->assertEquals($this->defaultConfigs(), $config); + $this->assertEquals($this->defaultConfigs(), $config->toArray()); $app->flush(); } @@ -106,6 +109,7 @@ public function itCanTerminateAfterApplicationDone() public function itCanDetectMaintenenceMode() { $app = new Application(__DIR__); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); $this->assertFalse($app->isDownMaintenanceMode()); @@ -138,6 +142,7 @@ public function itCanGetDownDefault() { $app = new Application('/'); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); $this->assertEquals([ 'redirect' => null, 'retry' => null, @@ -214,6 +219,7 @@ public function itCanAddCallImediatllyIfApplicationAlredyBooted() public function itCanCallDeprecatedMethod() { $app = new Application(__DIR__); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); $this->assertEquals($app->basePath(), $app->base_path()); $this->assertEquals($app->appPath(), $app->app_path()); diff --git a/tests/Integrate/Bootstrap/ConfigProvidersTest.php b/tests/Integrate/Bootstrap/ConfigProvidersTest.php new file mode 100644 index 00000000..b46c39ec --- /dev/null +++ b/tests/Integrate/Bootstrap/ConfigProvidersTest.php @@ -0,0 +1,63 @@ +bootstrap($app); + /** @var Config */ + $config = $app->get('config'); + + $this->assertEquals('dev', $config->get('ENVIRONMENT')); + + $app->flush(); + } + + /** + * @test + */ + public function itCanLoadConfigFromFile() + { + $app = new Application(dirname(__DIR__)); + + $app->setConfigPath(DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR); + (new ConfigProviders())->bootstrap($app); + /** @var Config */ + $config = $app->get('config'); + + $this->assertEquals('test', $config->get('ENVIRONMENT')); + + $app->flush(); + } + + /** + * Assume this test is boostrappe application. + * + * @test + */ + public function itCanLoadConfigFromCache() + { + $app = new Application(__DIR__ . DIRECTORY_SEPARATOR); + + $app->setConfigPath(DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR); + (new ConfigProviders())->bootstrap($app); + /** @var Config */ + $config = $app->get('config'); + + $this->assertEquals('prod', $config->get('ENVIRONMENT')); + + $app->flush(); + } +} diff --git a/tests/Integrate/Bootstrap/bootsrap/cache/config.php b/tests/Integrate/Bootstrap/bootsrap/cache/config.php new file mode 100644 index 00000000..10d726ba --- /dev/null +++ b/tests/Integrate/Bootstrap/bootsrap/cache/config.php @@ -0,0 +1,61 @@ + '/', + 'time_zone' => 'Asia/Jakarta', + 'APP_KEY' => '', + 'ENVIRONMENT' => 'prod', + + 'COMMAND_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Commands' . DIRECTORY_SEPARATOR, + 'CONTROLLER_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Controllers' . DIRECTORY_SEPARATOR, + 'MODEL_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR, + 'MIDDLEWARE' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Middlewares' . DIRECTORY_SEPARATOR, + 'SERVICE_PROVIDER' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Providers' . DIRECTORY_SEPARATOR, + 'CONFIG' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR, + 'SERVICES_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR, + 'VIEW_PATH' => DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR, + 'COMPONENT_PATH' => DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR, + 'STORAGE_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR, + 'CACHE_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR, + 'CACHE_VIEW_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR, + 'PUBLIC_PATH' => DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR, + 'MIGRATION_PATH' => DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migration' . DIRECTORY_SEPARATOR, + 'SEEDER_PATH' => DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeders' . DIRECTORY_SEPARATOR, + + 'PROVIDERS' => [ + // provider class name + ], + + // db config + 'DB_HOST' => 'localhost', + 'DB_USER' => 'root', + 'DB_PASS' => '', + 'DB_NAME' => '', + + // pusher + 'PUSHER_APP_ID' => '', + 'PUSHER_APP_KEY' => '', + 'PUSHER_APP_SECRET' => '', + 'PUSHER_APP_CLUSTER' => '', + + // redis driver + 'REDIS_HOST' => '127.0.0.1', + 'REDIS_PASS' => '', + 'REDIS_PORT' => 6379, + + // memcahe + 'MEMCACHED_HOST' => '127.0.0.1', + 'MEMCACHED_PASS' => '', + 'MEMCACHED_PORT' => 6379, + + // view config + 'VIEW_PATHS' => [ + DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR, + ], + 'VIEW_EXTENSIONS' => [ + '.template.php', + '.php', + ], + 'COMPILED_VIEW_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR, +]; diff --git a/tests/Integrate/Commands/ViewCommandsTest.php b/tests/Integrate/Commands/ViewCommandsTest.php index f9fc9684..105a2aab 100644 --- a/tests/Integrate/Commands/ViewCommandsTest.php +++ b/tests/Integrate/Commands/ViewCommandsTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use System\Integrate\Application; +use System\Integrate\ConfigRepository; use System\Integrate\Console\ViewCommand; use System\View\Templator; use System\View\TemplatorFinder; @@ -49,7 +50,9 @@ public function itCanCompileFromTemplatorFiles(): void public function itCanClearCompiledViewFile(): void { // tests\Integrate\Commands\assets\view_cache - (new Application(''))->setCachePath(__DIR__ . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'view_cache' . DIRECTORY_SEPARATOR); + $app = new Application(''); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); + $app->setCachePath(__DIR__ . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'view_cache' . DIRECTORY_SEPARATOR); file_put_contents(cache_path() . 'test01.php', ''); file_put_contents(cache_path() . 'test02.php', ''); diff --git a/tests/Integrate/ConfigRepositoryTest.php b/tests/Integrate/ConfigRepositoryTest.php new file mode 100644 index 00000000..9d1fab98 --- /dev/null +++ b/tests/Integrate/ConfigRepositoryTest.php @@ -0,0 +1,49 @@ + 'test', + 'num' => 1, + 'allow' => true, + ]; + + $config = new ConfigRepository($env); + // get + $this->assertEquals('test', $config->get('envi', 'local')); + // set + $config->set('envi', 'local'); + $this->assertEquals('local', (fn () => $this->{'config'}['envi'])->call($config)); + // has + $this->assertTrue($config->has('num')); + } + + /** @test */ + public function itCanPerformRepositoryUsingArrayAccess() + { + $env = [ + 'envi' => 'test', + 'num' => 1, + 'allow' => true, + ]; + + $config = new ConfigRepository($env); + + // get + $this->assertEquals('test', $config['envi']); + // set + $config['envi'] = 'local'; + $this->assertEquals('local', (fn () => $this->{'config'}['envi'])->call($config)); + // has + $this->assertTrue(isset($config['num'])); + // unset + unset($config['allow']); + $this->assertNull((fn () => $this->{'config'}['allow'])->call($config)); + } +} diff --git a/tests/Integrate/Exceptions/HandlerTest.php b/tests/Integrate/Exceptions/HandlerTest.php index 1e559036..292f00e4 100644 --- a/tests/Integrate/Exceptions/HandlerTest.php +++ b/tests/Integrate/Exceptions/HandlerTest.php @@ -104,7 +104,10 @@ public function itCanReportException() /** @test */ public function itCanRenderJson() { - $this->app->set('environment', 'prod'); + $this->app->bootedCallback(function () { + $this->app->set('environment', 'prod'); + }); + $karnel = $this->app->make(Karnel::class); $response = $karnel->handle(new Request('/test', [], [], [], [], [], [ 'content-type' => 'application/json', diff --git a/tests/Integrate/Http/Middleware/PreventRequestInMaintenanceTest.php b/tests/Integrate/Http/Middleware/PreventRequestInMaintenanceTest.php index dbd54846..1058c534 100644 --- a/tests/Integrate/Http/Middleware/PreventRequestInMaintenanceTest.php +++ b/tests/Integrate/Http/Middleware/PreventRequestInMaintenanceTest.php @@ -8,6 +8,7 @@ use System\Http\Request; use System\Http\Response; use System\Integrate\Application; +use System\Integrate\ConfigRepository; use System\Integrate\Http\Exception\HttpException; use System\Integrate\Http\Middleware\MaintenanceMiddleware; @@ -19,6 +20,7 @@ final class PreventRequestInMaintenanceTest extends TestCase public function itCanPreventRequestDuringMaintenance() { $app = new Application(__DIR__); + $app->loadConfig(new ConfigRepository($app->defaultConfigs())); $middleware = new MaintenanceMiddleware($app); $response = new Response('test'); $handle = $middleware->handle(new Request('/'), fn (Request $request) => $response); diff --git a/tests/Integrate/assets/Config/app.config.php b/tests/Integrate/assets/Config/app.config.php new file mode 100644 index 00000000..f47bb74f --- /dev/null +++ b/tests/Integrate/assets/Config/app.config.php @@ -0,0 +1,61 @@ + '/', + 'time_zone' => 'Asia/Jakarta', + 'APP_KEY' => '', + 'ENVIRONMENT' => 'test', + + 'COMMAND_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Commands' . DIRECTORY_SEPARATOR, + 'CONTROLLER_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Controllers' . DIRECTORY_SEPARATOR, + 'MODEL_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR, + 'MIDDLEWARE' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Middlewares' . DIRECTORY_SEPARATOR, + 'SERVICE_PROVIDER' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Providers' . DIRECTORY_SEPARATOR, + 'CONFIG' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR, + 'SERVICES_PATH' => DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR, + 'VIEW_PATH' => DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR, + 'COMPONENT_PATH' => DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR, + 'STORAGE_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR, + 'CACHE_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR, + 'CACHE_VIEW_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR, + 'PUBLIC_PATH' => DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR, + 'MIGRATION_PATH' => DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migration' . DIRECTORY_SEPARATOR, + 'SEEDER_PATH' => DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeders' . DIRECTORY_SEPARATOR, + + 'PROVIDERS' => [ + // provider class name + ], + + // db config + 'DB_HOST' => 'localhost', + 'DB_USER' => 'root', + 'DB_PASS' => '', + 'DB_NAME' => '', + + // pusher + 'PUSHER_APP_ID' => '', + 'PUSHER_APP_KEY' => '', + 'PUSHER_APP_SECRET' => '', + 'PUSHER_APP_CLUSTER' => '', + + // redis driver + 'REDIS_HOST' => '127.0.0.1', + 'REDIS_PASS' => '', + 'REDIS_PORT' => 6379, + + // memcahe + 'MEMCACHED_HOST' => '127.0.0.1', + 'MEMCACHED_PASS' => '', + 'MEMCACHED_PORT' => 6379, + + // view config + 'VIEW_PATHS' => [ + DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR, + ], + 'VIEW_EXTENSIONS' => [ + '.template.php', + '.php', + ], + 'COMPILED_VIEW_PATH' => DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR, +];