diff --git a/config.example.php b/config.example.php index 556f639..3cca2b6 100644 --- a/config.example.php +++ b/config.example.php @@ -14,6 +14,8 @@ 'environment' => [ // site debugging (set false in production) 'displayErrorDetails' => false, + // site debugging (set false in production) + 'debug' => true, // site profiling (benchmarking) // profiling is consume high memory footprints (set false in production) 'profiling' => false, @@ -60,7 +62,7 @@ // dev mode for development environment without cache (set false in production) 'devMode' => false, // proxy entities directory - 'proxyDir' => __DIR__ . '/storage/database/proxy', + 'proxyDirectory' => __DIR__ . '/storage/database/proxy', // additional doctrine config 'options' => [ ], diff --git a/src/Bin.php b/src/Bin.php index e0ea8b1..8183134 100644 --- a/src/Bin.php +++ b/src/Bin.php @@ -4,7 +4,7 @@ namespace ArrayAccess\TrayDigita; use ArrayAccess\TrayDigita\Console\Application; -use ArrayAccess\TrayDigita\Container\Container; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Http\Exceptions\HttpException; use ArrayAccess\TrayDigita\Http\ServerRequest; use ArrayAccess\TrayDigita\Kernel\Decorator; @@ -45,7 +45,6 @@ final private function __construct() /** * @noinspection PhpMissingReturnTypeInspection * @noinspection PhpIssetCanBeReplacedWithCoalesceInspection - * @noinspection DuplicatedCode * @throws Throwable */ final public static function run() @@ -106,22 +105,22 @@ final public static function run() chdir($root); # TD_APP_DIRECTORY='app' php bin/console if (!defined('TD_APP_DIRECTORY')) { - $appDir = getenv('TD_APP_DIRECTORY')?: null; - if ($appDir && is_string($appDir)) { - $appDir = realpath($appDir) ?: ( - realpath($cwd . DIRECTORY_SEPARATOR . $appDir) ?: ( - realpath(TD_ROOT_COMPOSER_DIR . DIRECTORY_SEPARATOR . $appDir) ?: null + $appDirectory = getenv('TD_APP_DIRECTORY')?: null; + if ($appDirectory && is_string($appDirectory)) { + $appDirectory = realpath($appDirectory) ?: ( + realpath($cwd . DIRECTORY_SEPARATOR . $appDirectory) ?: ( + realpath(TD_ROOT_COMPOSER_DIR . DIRECTORY_SEPARATOR . $appDirectory) ?: null ) ); } - $appDir = isset($appDir) ? $appDir : TD_ROOT_COMPOSER_DIR . DIRECTORY_SEPARATOR . 'app'; - /*if (!is_dir($appDir)) { + $appDirectory = isset($appDirectory) ? $appDirectory : TD_ROOT_COMPOSER_DIR . DIRECTORY_SEPARATOR . 'app'; + /*if (!is_dir($appDirectory)) { echo "\n\033[0;31mCould not detect application directory\033[0m\n"; echo "\n"; exit(255); }*/ - define('TD_APP_DIRECTORY', $appDir); + define('TD_APP_DIRECTORY', $appDirectory); } elseif (!is_string(TD_APP_DIRECTORY)) { echo "\n\033[0;31mConstant \033[0;0m`TD_APP_DIRECTORY`\033[0;31m is invalid\033[0m\n"; echo "\n"; @@ -158,7 +157,7 @@ final public static function run() $kernel->boot(); /** - * @var Container $container + * @var SystemContainerInterface $container */ $container = $kernel->getContainer(); $manager = $kernel->getManager(); diff --git a/src/ComposerCreateProject.php b/src/ComposerCreateProject.php index d275bbb..c836713 100644 --- a/src/ComposerCreateProject.php +++ b/src/ComposerCreateProject.php @@ -64,36 +64,36 @@ public static function composerDoCreateProject($event) return; } - $installDir = getcwd(); + $installDirectory = getcwd(); ///* // $package = $event->getComposer()->getPackage(); // $event->getComposer()->getInstallationManager()->getInstallPath($package); $prefixUnix = DIRECTORY_SEPARATOR === '/' ? "#!/usr/bin/env php\n" : ''; - $vendorDir = $event->getComposer()->getConfig()->get('vendor-dir'); + $vendorDirectory = $event->getComposer()->getConfig()->get('vendor-dir'); $consoleIO->write( - sprintf('Install Directory [%s]', $installDir), + sprintf('Install Directory [%s]', $installDirectory), true, $consoleIO::VERY_VERBOSE ); $consoleIO->write( - sprintf('Vendor Directory [%s]', $vendorDir), + sprintf('Vendor Directory [%s]', $vendorDirectory), true, $consoleIO::VERY_VERBOSE ); - if (!$vendorDir) { + if (!$vendorDirectory) { return; } - $vendorDir = rtrim($vendorDir, '\\/'); - $installDir = realpath($installDir)?:$installDir; - $installDir = rtrim($installDir, '\\/'); - $vendorDir = str_replace('\\', '/', $vendorDir); - $installDir = str_replace('\\', '/', $installDir); - if (!str_starts_with($vendorDir, $installDir . '/')) { + $vendorDirectory = rtrim($vendorDirectory, '\\/'); + $installDirectory = realpath($installDirectory)?:$installDirectory; + $installDirectory = rtrim($installDirectory, '\\/'); + $vendorDirectory = str_replace('\\', '/', $vendorDirectory); + $installDirectory = str_replace('\\', '/', $installDirectory); + if (!str_starts_with($vendorDirectory, $installDirectory . '/')) { return; } - $vendor = substr($vendorDir, strlen($installDir) + 1); + $vendor = substr($vendorDirectory, strlen($installDirectory) + 1); $vendor = addcslashes($vendor, "'"); $createFiles = [ 'data/README.md' => <<write('Preparing to create application structures'); - if (!file_exists($installDir . DIRECTORY_SEPARATOR . 'config.php')) { - if (!is_dir($installDir)) { - mkdir($installDir, 0755, true); + if (!file_exists($installDirectory . DIRECTORY_SEPARATOR . 'config.php')) { + if (!is_dir($installDirectory)) { + mkdir($installDirectory, 0755, true); } $configFile = file_get_contents(dirname(__DIR__) .'/config.example.php'); $configFile = str_replace( @@ -370,15 +370,15 @@ public static function composerDoCreateProject($event) $consoleIO->write( sprintf( '[GENERATING CONFIG] %s', - $installDir . '/config.php' + $installDirectory . '/config.php' ), true, $consoleIO::VERBOSE ); - file_put_contents($installDir . '/config.php', $configFile); + file_put_contents($installDirectory . '/config.php', $configFile); } foreach ($createFiles as $pathName => $content) { - $path = $installDir . DIRECTORY_SEPARATOR . $pathName; + $path = $installDirectory . DIRECTORY_SEPARATOR . $pathName; $isConsole = $pathName === 'bin/tray-digita'; if (file_exists($path)) { if (!$isConsole || is_link($path)) { @@ -418,7 +418,7 @@ public static function composerDoCreateProject($event) chmod($path, 0744); } } - $langDir = $installDir . '/app/Languages'; + $langDirectory = $installDirectory . '/app/Languages'; $consoleIO->write( 'Copying language files', true, @@ -436,10 +436,10 @@ public static function composerDoCreateProject($event) continue; } $baseName = $directory->getBasename(); - if (file_exists($langDir . DIRECTORY_SEPARATOR . $baseName)) { + if (file_exists($langDirectory . DIRECTORY_SEPARATOR . $baseName)) { continue; } - copy($directory->getRealPath(), $langDir . DIRECTORY_SEPARATOR . $baseName); + copy($directory->getRealPath(), $langDirectory . DIRECTORY_SEPARATOR . $baseName); } $consoleIO->write( 'Done', diff --git a/src/Console/Command/ApplicationChecker/ContainerChecker.php b/src/Console/Command/ApplicationChecker/ContainerChecker.php index 56844ca..e62cd74 100644 --- a/src/Console/Command/ApplicationChecker/ContainerChecker.php +++ b/src/Console/Command/ApplicationChecker/ContainerChecker.php @@ -4,7 +4,7 @@ namespace ArrayAccess\TrayDigita\Console\Command\ApplicationChecker; use ArrayAccess\TrayDigita\Console\Command\Traits\WriterHelperTrait; -use ArrayAccess\TrayDigita\Container\Container; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -36,7 +36,7 @@ public function check(InputInterface $input, OutputInterface $output) : int true ); if ($output->isVerbose()) { - if ($container instanceof Container) { + if ($container instanceof SystemContainerInterface) { $output->writeln('', OutputInterface::VERBOSITY_VERY_VERBOSE); $this->writeIndent( $output, diff --git a/src/Console/Command/CommandGenerator.php b/src/Console/Command/CommandGenerator.php index 07077dd..b031630 100644 --- a/src/Console/Command/CommandGenerator.php +++ b/src/Console/Command/CommandGenerator.php @@ -49,7 +49,7 @@ class CommandGenerator extends Command implements ContainerAllocatorInterface, M ManagerAllocatorTrait, TranslatorTrait; - private ?string $commandDir = null; + private ?string $commandDirectory = null; private string $commandNamespace; protected function configure() : void @@ -60,7 +60,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->commandNamespace = $kernel->getCommandNameSpace(); - $this->commandDir = $kernel->getRegisteredDirectories()[$this->commandNamespace]??null; + $this->commandDirectory = $kernel->getRegisteredDirectories()[$this->commandNamespace]??null; } else { $namespace = dirname( str_replace( @@ -140,7 +140,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->commandList === null) { $this->commandList = []; - $commandDirectory = $this->commandDir; + $commandDirectory = $this->commandDirectory; $lengthStart = strlen($commandDirectory) + 1; foreach (Finder::create() ->in($commandDirectory) @@ -241,17 +241,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->commandDir) { + if (!$this->commandDirectory) { $config = ContainerHelper::getNull(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $commandDir = $path?->get('command'); - if (is_string($commandDir) && is_dir($commandDir)) { - $this->commandDir = realpath($commandDir) ?? $commandDir; + $commandDirectory = $path?->get('command'); + if (is_string($commandDirectory) && is_dir($commandDirectory)) { + $this->commandDirectory = realpath($commandDirectory) ?? $commandDirectory; } } - if (!$this->commandDir) { + if (!$this->commandDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -278,7 +278,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->commandDir + $fileName = $this->commandDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . '.php'; diff --git a/src/Console/Command/ControllerGenerator.php b/src/Console/Command/ControllerGenerator.php index 805974f..ba2adc3 100644 --- a/src/Console/Command/ControllerGenerator.php +++ b/src/Console/Command/ControllerGenerator.php @@ -49,7 +49,7 @@ class ControllerGenerator extends Command implements ContainerAllocatorInterface ManagerAllocatorTrait, TranslatorTrait; - private ?string $controllerDir = null; + private ?string $controllerDirectory = null; private string $controllerNameSpace; @@ -61,7 +61,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->controllerNameSpace = $kernel->getControllerNameSpace(); - $this->controllerDir = $kernel->getRegisteredDirectories()[$this->controllerNameSpace]??null; + $this->controllerDirectory = $kernel->getRegisteredDirectories()[$this->controllerNameSpace]??null; } else { $namespace = dirname( str_replace( @@ -131,7 +131,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->controllerList === null) { $this->controllerList = []; - $controllerDirectory = $this->controllerDir; + $controllerDirectory = $this->controllerDirectory; $lengthStart = strlen($controllerDirectory) + 1; foreach (Finder::create() ->in($controllerDirectory) @@ -211,17 +211,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->controllerDir) { + if (!$this->controllerDirectory) { $config = ContainerHelper::getNull(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $controllerDir = $path?->get('controller'); - if (is_string($controllerDir) && is_dir($controllerDir)) { - $this->controllerDir = realpath($controllerDir) ?? $controllerDir; + $controllerDirectory = $path?->get('controller'); + if (is_string($controllerDirectory) && is_dir($controllerDirectory)) { + $this->controllerDirectory = realpath($controllerDirectory) ?? $controllerDirectory; } } - if (!$this->controllerDir) { + if (!$this->controllerDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -248,7 +248,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->controllerDir + $fileName = $this->controllerDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $name) . '.php'; diff --git a/src/Console/Command/DatabaseEventGenerator.php b/src/Console/Command/DatabaseEventGenerator.php index 3e13348..2ac1330 100644 --- a/src/Console/Command/DatabaseEventGenerator.php +++ b/src/Console/Command/DatabaseEventGenerator.php @@ -49,7 +49,7 @@ class DatabaseEventGenerator extends Command implements ContainerAllocatorInterf ManagerAllocatorTrait, TranslatorTrait; - private ?string $databaseEventDir = null; + private ?string $databaseEventDirectory = null; private string $databaseEventNamespace; protected function configure() : void @@ -60,7 +60,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->databaseEventNamespace = $kernel->getDatabaseEventNameSpace(); - $this->databaseEventDir = $kernel->getRegisteredDirectories()[$this->databaseEventNamespace]??null; + $this->databaseEventDirectory = $kernel->getRegisteredDirectories()[$this->databaseEventNamespace]??null; } else { $namespace = dirname( str_replace( @@ -143,7 +143,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->databaseEventList === null) { $this->databaseEventList = []; - $databaseEventDirectory = $this->databaseEventDir; + $databaseEventDirectory = $this->databaseEventDirectory; $lengthStart = strlen($databaseEventDirectory) + 1; foreach (Finder::create() ->in($databaseEventDirectory) @@ -244,12 +244,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $config = ContainerHelper::use(Config::class, $container)??new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $databaseEventDir = $path?->get('databaseEvent'); - if (is_string($databaseEventDir) && is_dir($databaseEventDir)) { - $this->databaseEventDir = realpath($databaseEventDir)??$databaseEventDir; + $databaseEventDirectory = $path?->get('databaseEvent'); + if (is_string($databaseEventDirectory) && is_dir($databaseEventDirectory)) { + $this->databaseEventDirectory = realpath($databaseEventDirectory)??$databaseEventDirectory; } - if (!$this->databaseEventDir) { + if (!$this->databaseEventDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -276,7 +276,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->databaseEventDir + $fileName = $this->databaseEventDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . '.php'; diff --git a/src/Console/Command/EntityGenerator.php b/src/Console/Command/EntityGenerator.php index 2fcead5..e2611b6 100644 --- a/src/Console/Command/EntityGenerator.php +++ b/src/Console/Command/EntityGenerator.php @@ -49,7 +49,7 @@ class EntityGenerator extends Command implements ContainerAllocatorInterface, Ma ManagerAllocatorTrait, TranslatorTrait; - private ?string $entityDir = null; + private ?string $entityDirectory = null; private string $entityNamespace; protected function configure() : void @@ -60,7 +60,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->entityNamespace = $kernel->getEntityNamespace(); - $this->entityDir = $kernel->getRegisteredDirectories()[$this->entityNamespace]??null; + $this->entityDirectory = $kernel->getRegisteredDirectories()[$this->entityNamespace]??null; } else { $namespace = dirname( str_replace( @@ -138,10 +138,10 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->entityLists === null) { $this->entityLists = []; - $entityDir = $this->entityDir; - $lengthStart = strlen($entityDir) + 1; + $entityDirectory = $this->entityDirectory; + $lengthStart = strlen($entityDirectory) + 1; foreach (Finder::create() - ->in($entityDir) + ->in($entityDirectory) ->ignoreVCS(true) ->ignoreDotFiles(true) // depth <= 10 @@ -225,16 +225,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->entityDir) { + if (!$this->entityDirectory) { $config = ContainerHelper::use(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $entityDir = $path?->get('entity'); - if (is_string($entityDir) && is_dir($entityDir)) { - $this->entityDir = realpath($entityDir) ?? $entityDir; + $entityDirectory = $path?->get('entity'); + if (is_string($entityDirectory) && is_dir($entityDirectory)) { + $this->entityDirectory = realpath($entityDirectory) ?? $entityDirectory; } } - if (!$this->entityDir) { + if (!$this->entityDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -261,7 +261,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->entityDir + $fileName = $this->entityDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . '.php'; diff --git a/src/Console/Command/MiddlewareGenerator.php b/src/Console/Command/MiddlewareGenerator.php index ceadde0..ce2bcf5 100644 --- a/src/Console/Command/MiddlewareGenerator.php +++ b/src/Console/Command/MiddlewareGenerator.php @@ -49,7 +49,7 @@ class MiddlewareGenerator extends Command implements ContainerAllocatorInterface ManagerAllocatorTrait, TranslatorTrait; - private ?string $middlewareDir = null; + private ?string $middlewareDirectory = null; private string $middlewareNamespace; @@ -61,7 +61,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->middlewareNamespace = $kernel->getMiddlewareNamespace(); - $this->middlewareDir = $kernel->getRegisteredDirectories()[$this->middlewareNamespace]??null; + $this->middlewareDirectory = $kernel->getRegisteredDirectories()[$this->middlewareNamespace]??null; } else { $namespace = dirname( str_replace( @@ -139,7 +139,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->middlewareList === null) { $this->middlewareList = []; - $middlewareDirectory = $this->middlewareDir; + $middlewareDirectory = $this->middlewareDirectory; $lengthStart = strlen($middlewareDirectory) + 1; foreach (Finder::create() ->in($middlewareDirectory) @@ -235,17 +235,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->middlewareDir) { + if (!$this->middlewareDirectory) { $config = ContainerHelper::use(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $middlewareDir = $path?->get('middleware'); - if (is_string($middlewareDir) && is_dir($middlewareDir)) { - $this->middlewareDir = realpath($middlewareDir) ?? $middlewareDir; + $middlewareDirectory = $path?->get('middleware'); + if (is_string($middlewareDirectory) && is_dir($middlewareDirectory)) { + $this->middlewareDirectory = realpath($middlewareDirectory) ?? $middlewareDirectory; } } - if (!$this->middlewareDir) { + if (!$this->middlewareDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -272,7 +272,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->middlewareDir + $fileName = $this->middlewareDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . '.php'; diff --git a/src/Console/Command/ModuleGenerator.php b/src/Console/Command/ModuleGenerator.php index c01d96c..a4b03e8 100644 --- a/src/Console/Command/ModuleGenerator.php +++ b/src/Console/Command/ModuleGenerator.php @@ -49,7 +49,7 @@ class ModuleGenerator extends Command implements ContainerAllocatorInterface, Ma ManagerAllocatorTrait, TranslatorTrait; - private ?string $moduleDir = null; + private ?string $moduleDirectory = null; private string $moduleNamespace; protected function configure() : void @@ -60,7 +60,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->moduleNamespace = $kernel->getControllerNameSpace(); - $this->moduleDir = $kernel->getRegisteredDirectories()[$this->moduleNamespace]??null; + $this->moduleDirectory = $kernel->getRegisteredDirectories()[$this->moduleNamespace]??null; } else { $namespace = dirname( str_replace( @@ -139,7 +139,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->moduleList === null) { $this->moduleList = []; - $moduleDirectory = $this->moduleDir; + $moduleDirectory = $this->moduleDirectory; $lengthStart = strlen($moduleDirectory) + 1; foreach (Finder::create() ->in($moduleDirectory) @@ -240,17 +240,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->moduleDir) { + if (!$this->moduleDirectory) { $config = ContainerHelper::use(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $moduleDir = $path?->get('module'); - if (is_string($moduleDir) && is_dir($moduleDir)) { - $this->moduleDir = realpath($moduleDir) ?? $moduleDir; + $moduleDirectory = $path?->get('module'); + if (is_string($moduleDirectory) && is_dir($moduleDirectory)) { + $this->moduleDirectory = realpath($moduleDirectory) ?? $moduleDirectory; } } - if (!$this->moduleDir) { + if (!$this->moduleDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -277,7 +277,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->moduleDir + $fileName = $this->moduleDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . DIRECTORY_SEPARATOR diff --git a/src/Console/Command/SchedulerGenerator.php b/src/Console/Command/SchedulerGenerator.php index a781c11..8f2158c 100644 --- a/src/Console/Command/SchedulerGenerator.php +++ b/src/Console/Command/SchedulerGenerator.php @@ -49,7 +49,7 @@ class SchedulerGenerator extends Command implements ContainerAllocatorInterface, ManagerAllocatorTrait, TranslatorTrait; - private ?string $schedulerDir = null; + private ?string $schedulerDirectory = null; private string $schedulerNamespace; @@ -61,7 +61,7 @@ protected function configure() : void ); if ($kernel instanceof BaseKernel) { $this->schedulerNamespace = $kernel->getSchedulerNamespace(); - $this->schedulerDir = $kernel->getRegisteredDirectories()[$this->schedulerNamespace]??null; + $this->schedulerDirectory = $kernel->getRegisteredDirectories()[$this->schedulerNamespace]??null; } else { $namespace = dirname( str_replace( @@ -141,7 +141,7 @@ private function isFileExists(string $className) : bool $lowerClassName = strtolower($className); if ($this->schedulerList === null) { $this->schedulerList = []; - $schedulerDirectory = $this->schedulerDir; + $schedulerDirectory = $this->schedulerDirectory; $lengthStart = strlen($schedulerDirectory) + 1; foreach (Finder::create() ->in($schedulerDirectory) @@ -245,18 +245,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input->setInteractive(true); $container = $this->getContainer(); - if (!$this->schedulerDir) { + if (!$this->schedulerDirectory) { $config = ContainerHelper::use(Config::class, $container) ?? new Config(); $path = $config->get('path'); $path = $path instanceof Config ? $path : null; - $schedulerDir = $path?->get('scheduler'); - if (is_string($schedulerDir) && is_dir($schedulerDir)) { - $this->schedulerDir = realpath($schedulerDir) ?? $schedulerDir; + $schedulerDirectory = $path?->get('scheduler'); + if (is_string($schedulerDirectory) && is_dir($schedulerDirectory)) { + $this->schedulerDirectory = realpath($schedulerDirectory) ?? $schedulerDirectory; } } - if (!$this->schedulerDir) { + if (!$this->schedulerDirectory) { $output->writeln( sprintf( $this->translateContext( @@ -283,7 +283,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::FAILURE; } - $fileName = $this->schedulerDir + $fileName = $this->schedulerDirectory . DIRECTORY_SEPARATOR . str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $named['className']) . '.php'; diff --git a/src/Container/Container.php b/src/Container/Container.php index 7aeaa2e..9123b1a 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -3,16 +3,14 @@ namespace ArrayAccess\TrayDigita\Container; -use ArrayAccess; use ArrayAccess\TrayDigita\Container\Exceptions\ContainerFrozenException; use ArrayAccess\TrayDigita\Container\Exceptions\ContainerNotFoundException; -use ArrayAccess\TrayDigita\Container\Interfaces\UnInvokableInterface; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Container\Traits\ContainerDecorator; use ArrayAccess\TrayDigita\Exceptions\Logical\InvokeAbleException; use ArrayAccess\TrayDigita\Traits\Service\CallStackTraceTrait; use ArrayAccess\TrayDigita\Util\Filter\Consolidation; use Psr\Container\ContainerExceptionInterface; -use Psr\Container\ContainerInterface; use SensitiveParameter; use Throwable; use function array_key_exists; @@ -23,7 +21,7 @@ /** * @template T of object */ -class Container implements ContainerInterface, ArrayAccess, UnInvokableInterface +class Container implements SystemContainerInterface { use CallStackTraceTrait, ContainerDecorator; @@ -235,6 +233,7 @@ public function getFrozenServices(): array */ public function get(string $id) { + /** @noinspection DuplicatedCode */ if ($this->hasRawService($id)) { $this->frozenServices[$id] ??= true; return $this->getRawService($id); @@ -259,7 +258,6 @@ public function get(string $id) $this->assertCallstack(); $value = $this->getResolver()->resolve($id); $this->removeQueuedService($id); - // call $this->raw($id, $value); $this->frozenServices[$id] = true; diff --git a/src/Container/ContainerResolver.php b/src/Container/ContainerResolver.php index fb89816..638785d 100644 --- a/src/Container/ContainerResolver.php +++ b/src/Container/ContainerResolver.php @@ -6,6 +6,7 @@ use ArrayAccess\TrayDigita\Container\Exceptions\ContainerNotFoundException; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerAllocatorInterface; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Container\Interfaces\UnInvokableInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; @@ -172,7 +173,7 @@ public function allocateService($containerValue) : void public function resolve(string $id): mixed { $container = $this->getContainer(); - if (!$container instanceof Container || !$container->hasQueuedService($id)) { + if (!$container instanceof SystemContainerInterface || !$container->hasQueuedService($id)) { throw new ContainerNotFoundException( sprintf('Queued container service %s has not found.', $id) ); @@ -204,8 +205,14 @@ private function resolveTheArgumentObjectBuiltin( return null; } if ($type->isBuiltin()) { + $builtin = [ + 'bool' => 'boolean', + 'float' => 'double', + ]; $argType = gettype($argumentValue); - $found = $argType === $type->getName(); + $argType = $builtin[$argType]??$argType; + $argName = $builtin[$type->getName()]??$type->getName(); + $found = $argType === $argName; return $found ? $argumentValue : null; } if (is_object($argumentValue) && is_a($argumentValue, $type->getName())) { @@ -291,7 +298,6 @@ private function resolveInternalArgument( } return; } - if (!$refType instanceof ReflectionNamedType) { return; } @@ -323,7 +329,9 @@ private function resolveInternalArgument( } } - if ($refName === ContainerInterface::class || is_a($refName, $this->container::class)) { + if ($refName === ContainerInterface::class + || $refName === SystemContainerInterface::class + || is_a($refName, $this->container::class)) { $paramFound = true; if ($this->container->has($refName)) { try { diff --git a/src/Container/ContainerWrapper.php b/src/Container/ContainerWrapper.php index 19502a2..b0c9aaf 100644 --- a/src/Container/ContainerWrapper.php +++ b/src/Container/ContainerWrapper.php @@ -3,52 +3,152 @@ namespace ArrayAccess\TrayDigita\Container; -use ArrayAccess; -use ArrayAccess\TrayDigita\Container\Interfaces\UnInvokableInterface; +use ArrayAccess\TrayDigita\Container\Exceptions\ContainerFrozenException; +use ArrayAccess\TrayDigita\Container\Exceptions\ContainerNotFoundException; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Container\Traits\ContainerDecorator; use ArrayAccess\TrayDigita\Exceptions\Logical\InvokeAbleException; +use ArrayAccess\TrayDigita\Traits\Service\CallStackTraceTrait; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; +use SensitiveParameter; use Throwable; +use function array_key_exists; +use function array_keys; +use function array_merge; use function method_exists; use function sprintf; -class ContainerWrapper implements ContainerInterface, ArrayAccess, UnInvokableInterface +final class ContainerWrapper implements SystemContainerInterface { - private ContainerInterface $container; + use ContainerDecorator, + CallStackTraceTrait; - use ContainerDecorator; + private ContainerInterface|SystemContainerInterface $container; - private ?ContainerResolver $resolver; + private ?ContainerResolver $resolver = null; + + /** + * @var array + */ + private array $aliases = []; + + private array $queuedServices = []; + + private array $arguments = []; + + private array $parameters = []; + + private array $rawServices = []; + + private array $frozenServices = []; public function __construct(ContainerInterface $container) { $this->container = $container; - if ($container instanceof Container) { - $this->resolver = $container->getResolver(); - } else { - $this->resolver = new ContainerResolver($container); + if (!$container instanceof SystemContainerInterface) { + $this->resolver = new ContainerResolver($this); + } + } + + /** + * @param string $id + * @return void + * @throws ContainerFrozenException + */ + private function assertFrozen(string $id): void + { + if ($this->isFrozen($id)) { + throw new ContainerFrozenException( + sprintf('Container %s has frozen', $id) + ); } } - public static function createFromContainer(ContainerInterface $container) : static + public function getContainer(): ContainerInterface { - return new static($container); + return $this->container; } - public static function maybeContainerOrCreate(ContainerInterface $container) : static|ContainerWrapper|Container + public static function createFromContainer(ContainerInterface $container) : ContainerWrapper { - if ($container instanceof ContainerWrapper || $container instanceof Container) { + return new ContainerWrapper($container); + } + + public static function maybeContainerOrCreate( + ContainerInterface $container + ) : SystemContainerInterface { + if ($container instanceof ContainerWrapper) { + $theContainer = $container->getContainer(); + if ($theContainer instanceof SystemContainerInterface) { + return $theContainer; + } + } + + if ($container instanceof SystemContainerInterface) { return $container; } - return static::createFromContainer($container); + return self::createFromContainer($container); } + /** + * @template T + * @param string|class-string $id + * @return T|mixed + * @throws ContainerFrozenException + * @throws ContainerNotFoundException + * @throws Throwable + */ public function get(string $id) { - return $this->container->get($id); + if ($this->container instanceof SystemContainerInterface + || $this->container->has($id) + ) { + return $this->container->get($id); + } + + /** @noinspection DuplicatedCode */ + if ($this->hasRawService($id)) { + $this->frozenServices[$id] ??= true; + return $this->getRawService($id); + } + + $exists = $this->hasQueuedService($id); + if (!$exists && $this->hasAlias($id)) { + $newId = $this->getAlias($id); + if ($this->hasRawService($newId)) { + $this->frozenServices[$id] ??= true; + return $this->getRawService($newId); + } + if ($this->hasQueuedService($newId)) { + $id = $newId; + $exists = true; + } + } + + if ($exists) { + try { + // assert + $this->assertCallstack(); + $value = $this->getResolver()->resolve($id); + $this->removeQueuedService($id); + // call + $this->raw($id, $value); + $this->frozenServices[$id] = true; + // reset + $this->resetCallstack(); + return $value; + } catch (Throwable $e) { + $this->resetCallstack(); + throw $e; + } + } + + throw new ContainerNotFoundException( + sprintf('Container %s has not found.', $id) + ); } public function has(string $id): bool @@ -58,37 +158,44 @@ public function has(string $id): bool public function remove(string $id): void { - if ($this->container instanceof Container) { - $this->container->remove($id); - return; - } - if (method_exists($this->container, 'remove')) { + if ($this->container instanceof SystemContainerInterface) { $this->container->remove($id); return; } - if ($this->container instanceof ArrayAccess) { - unset($this->container[$id]); - } + + unset( + $this->queuedServices[$id], + $this->frozenServices[$id], + $this->rawServices[$id], + $this->arguments[$id] + ); } public function set(string $id, mixed $container, ...$arguments): void { - if ($this->container instanceof Container) { + if ($this->container instanceof SystemContainerInterface) { $this->container->set($id, $container, ...$arguments); return; } + if (method_exists($this->container, 'set')) { - $this->container->set($id, $container, ...$arguments); + $this->container->set($id, function () use ($container, $arguments) { + return $this->getResolver()->resolveCallable($container, $arguments); + }); return; } - if ($this->container instanceof ArrayAccess) { - $this->container[$id] = $container; + + $this->assertFrozen($id); + unset($this->rawServices[$id]); + $this->queuedServices[$id] = $container; + if ($arguments === []) { + $this->arguments[$id] = $arguments; } } - public function getResolver(): ?ContainerResolver + public function getResolver(): ContainerResolver { - return $this->resolver; + return $this->resolver??$this->container->getResolver(); } public function offsetExists(mixed $offset): bool @@ -122,4 +229,202 @@ public function __invoke() sprintf('Class %s is not invokable', $this::class) ); } + + public function hasArgument(string $serviceId): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->hasArgument($serviceId); + } + return array_key_exists($serviceId, $this->arguments); + } + + public function getArgument(string $serviceId) + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getArgument($serviceId); + } + return $this->arguments[$serviceId]??null; + } + + public function setAlias(string $id, string $containerId): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->setAlias($id, $containerId); + return; + } + $this->aliases[$id] = $containerId; + } + + public function removeAlias(string $id): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->removeAlias($id); + return; + } + unset($this->aliases[$id]); + } + + public function getAliases(): array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getAliases(); + } + return $this->aliases; + } + + public function hasAlias(string $id): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->hasAlias($id); + } + return array_key_exists($id, $this->aliases); + } + + public function getAlias(string $id): ?string + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getAlias($id); + } + return $this->aliases[$id]??null; + } + + public function getParameter(string $name) + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getParameter($name); + } + return $this->parameters[$name]??null; + } + + public function getParameters(): array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getParameters(); + } + return $this->parameters; + } + + public function setParameters(array $parameters): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->setParameters($parameters); + return; + } + $this->parameters = $parameters; + } + + public function setParameter(string $name, #[SensitiveParameter] $value): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->setParameter($name, $value); + return; + } + $this->parameters[$name] = $value; + } + + public function add(ContainerInvokable $objectContainer, ...$arguments): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->add($objectContainer, ...$arguments); + return; + } + $this->set($objectContainer->getId(), $objectContainer, ...$arguments); + } + + public function raw(string $id, $raw): void + { + if ($this->container instanceof SystemContainerInterface) { + $this->container->raw($id, $raw); + return; + } + $this->assertFrozen($id); + $this->rawServices[$id] = $raw; + } + + public function getQueuedServices(): array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getQueuedServices(); + } + return $this->queuedServices; + } + + public function hasQueuedService(string $id): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->hasQueuedService($id); + } + return array_key_exists($id, $this->queuedServices); + } + + public function removeQueuedService(string $id): mixed + { + $value = $this->getQueueService($id); + unset($this->queuedServices[$id], $this->arguments[$id]); + return $value; + } + + public function inQueue(string $id): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->inQueue($id); + } + return array_key_exists($id, $this->queuedServices); + } + + public function getQueueService(string $id) : mixed + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getQueueService($id); + } + return $this->queuedServices[$id]??null; + } + + public function isFrozen(string $id): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->isFrozen($id); + } + return isset($this->frozenServices[$id]); + } + + public function getRawServices(): array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getRawServices(); + } + return $this->rawServices; + } + + public function getRawService(string $id) + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getRawServices(); + } + return $this->rawServices[$id]??null; + } + + public function hasRawService(string $id): bool + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->hasRawService($id); + } + return array_key_exists($id, $this->rawServices); + } + + public function getFrozenServices(): array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->getFrozenServices(); + } + return $this->frozenServices; + } + + public function keys() : array + { + if ($this->container instanceof SystemContainerInterface) { + return $this->container->keys(); + } + return array_merge(array_keys($this->queuedServices), array_keys($this->rawServices)); + } } diff --git a/src/Container/Factory/ContainerFactory.php b/src/Container/Factory/ContainerFactory.php index 0628580..14dc6ea 100644 --- a/src/Container/Factory/ContainerFactory.php +++ b/src/Container/Factory/ContainerFactory.php @@ -22,7 +22,9 @@ use ArrayAccess\TrayDigita\Collection\Config; use ArrayAccess\TrayDigita\Console\Application; use ArrayAccess\TrayDigita\Container\Container; +use ArrayAccess\TrayDigita\Container\ContainerWrapper; use ArrayAccess\TrayDigita\Container\Exceptions\ContainerFrozenException; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerFactoryInterface; use ArrayAccess\TrayDigita\Database\Connection; use ArrayAccess\TrayDigita\Database\DatabaseEventsCollector; @@ -43,6 +45,8 @@ use ArrayAccess\TrayDigita\Image\ImageResizer; use ArrayAccess\TrayDigita\Image\Interfaces\ImageResizerFactoryInterface; use ArrayAccess\TrayDigita\Kernel\HttpKernel; +use ArrayAccess\TrayDigita\Kernel\Interfaces\KernelInterface; +use ArrayAccess\TrayDigita\Kernel\Kernel; use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\TranslatorInterface; use ArrayAccess\TrayDigita\L10n\Translations\Translator; use ArrayAccess\TrayDigita\Logger\Logger; @@ -103,8 +107,12 @@ class ContainerFactory implements ContainerFactoryInterface RouteRunnerInterface::class => RouteRunner::class, MiddlewareDispatcherInterface::class => MiddlewareDispatcher::class, ResponseEmitterInterface::class => ResponseEmitter::class, + // kernel + KernelInterface::class => Kernel::class, HttpKernelInterface::class => HttpKernel::class, + // scheduler Scheduler::class => Scheduler::class, + // assets AssetsCollectionInterface::class => AssetCollection::class, // i18n TranslatorInterface::class => Translator::class, @@ -273,17 +281,20 @@ public function createContainer( foreach ($aliases as $id => $target) { $container->setAlias($id, $target); } + return $container; } /** - * @psalm-return Container|ContainerInterface + * @return SystemContainerInterface * @throws ContainerFrozenException */ - public function createDefault(): ContainerInterface + public function createDefault(): SystemContainerInterface { $default = array_merge($this->defaultServices, self::DEFAULT_SERVICES); $defaultAliases = array_merge($this->defaultServiceAliases, self::DEFAULT_SERVICE_ALIASES); - return $this->createContainer($default, $defaultAliases); + return ContainerWrapper::maybeContainerOrCreate( + $this->createContainer($default, $defaultAliases) + ); } } diff --git a/src/Container/Interfaces/SystemContainerInterface.php b/src/Container/Interfaces/SystemContainerInterface.php new file mode 100644 index 0000000..a6b998a --- /dev/null +++ b/src/Container/Interfaces/SystemContainerInterface.php @@ -0,0 +1,74 @@ +getSchemaAssetsFilter())) { $orm->setSchemaAssetsFilter($schema); } - $entityDir = null; + $entityDirectory = null; $storage = null; - $proxyDir = null; + $proxyDirectory = null; if (method_exists($container, 'getParameter')) { - $entityDir = $container->getParameter('entitiesDir'); - $storage = $container->getParameter('storageDir'); - $proxyDir = $container->getParameter('proxyDir'); + $entityDirectory = $container->getParameter('entitiesDirectory'); + $storage = $container->getParameter('storageDirectory'); + $proxyDirectory = $container->getParameter('proxyDirectory'); } $storage = is_string($storage) ? $storage : $path->get('storage'); - $entityDir = is_string($entityDir) ? $entityDir : $path->get('entity'); - $proxyPath = is_string($proxyDir) ? $proxyDir : $database->get('proxyDir'); + $entityDirectory = is_string($entityDirectory) ? $entityDirectory : $path->get('entity'); + $proxyPath = is_string($proxyDirectory) ? $proxyDirectory : $database->get('proxyDirectory'); $proxyPath = $proxyPath ?: "$storage/database/proxy"; $proxyPath = $configuration->getProxyDir() ?: $proxyPath; if (!is_dir($proxyPath) && ! Consolidation::isCli()) { @@ -158,13 +158,13 @@ protected function configureORMConfiguration(?Configuration $configuration) : Or : preg_replace('~(.+)\\\[^\\\]+$~', '$1\\Storage\Proxy', __NAMESPACE__); if (!$metadata instanceof AttributeDriver) { $metadata = new AttributeDriver( - [$entityDir], + [$entityDirectory], true ); } $metadata->setFileExtension('.php'); - $metadata->addPaths([$entityDir]); + $metadata->addPaths([$entityDirectory]); $orm->setMetadataDriverImpl($metadata); $orm->setProxyDir($proxyPath); $orm->setProxyNamespace($proxyNamesSpace); diff --git a/src/HttpKernel/BaseKernel.php b/src/HttpKernel/BaseKernel.php index 39b59b2..c57b0f9 100644 --- a/src/HttpKernel/BaseKernel.php +++ b/src/HttpKernel/BaseKernel.php @@ -7,7 +7,8 @@ use ArrayAccess\TrayDigita\Benchmark\Interfaces\ResetInterface; use ArrayAccess\TrayDigita\Benchmark\Middlewares\DebuggingMiddleware; use ArrayAccess\TrayDigita\Collection\Config; -use ArrayAccess\TrayDigita\Container\Container; +use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Exceptions\Runtime\RuntimeException; use ArrayAccess\TrayDigita\HttpKernel\Helper\KernelCommandLoader; @@ -30,6 +31,7 @@ use ArrayAccess\TrayDigita\Util\Filter\DataNormalizer; use ArrayAccess\TrayDigita\Util\Parser\DotEnv; use DateTimeZone; +use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -72,10 +74,9 @@ abstract class BaseKernel implements KernelInterface, RequestHandlerInterface, - RebootableInterface + RebootableInterface, + ContainerIndicateInterface { - const DEFAULT_EXPIRED_AFTER = 7200; - private int $bootStack = 0; private bool $booted = false; @@ -131,6 +132,14 @@ public function __construct( } } + /** + * @return ?ContainerInterface + */ + public function getContainer(): ?ContainerInterface + { + return $this->getHttpKernel()->getContainer(); + } + public function getStartMemory(): int { return $this->getHttpKernel()->getStartMemory(); @@ -175,16 +184,13 @@ public function boot() if ($this->booted === true) { if (!$this->requestStackSize && true === $this->resetServices - && $this - ->getHttpKernel() - ->getContainer() - ?->has(ResetInterface::class) + && ($resetter = ContainerHelper::getNull( + ResetInterface::class, + $this->getContainer() + )) ) { try { - $resetter = $this->getHttpKernel()->getContainer()?->get(ResetInterface::class); - if ($resetter instanceof ResetInterface) { - $resetter->reset(); - } + $resetter->reset(); } catch (Throwable) { } } @@ -387,7 +393,7 @@ final public function init() : static } /** - * @var Container $container + * @var SystemContainerInterface $container */ $httpKernel = $this->getHttpKernel(); $container = $httpKernel->getContainer(); @@ -401,17 +407,17 @@ final public function init() : static $container->remove(KernelInterface::class); $container->raw(KernelInterface::class, $this); - $appDir = realpath(TD_APP_DIRECTORY)?:TD_APP_DIRECTORY; + $appDirectory = realpath(TD_APP_DIRECTORY)?:TD_APP_DIRECTORY; $this->rootDirectory = $root; if (defined('TD_INDEX_FILE') && is_string(TD_INDEX_FILE) && file_exists(TD_INDEX_FILE) ) { - $publicDir = dirname(realpath(TD_INDEX_FILE)??TD_INDEX_FILE); + $publicDirectory = dirname(realpath(TD_INDEX_FILE)??TD_INDEX_FILE); } else { if (Consolidation::isCli()) { // use default public - $publicDir = $root . DIRECTORY_SEPARATOR . 'public'; + $publicDirectory = $root . DIRECTORY_SEPARATOR . 'public'; } else { $documentRoot = $_SERVER['DOCUMENT_ROOT']??null; $documentRoot = $documentRoot && is_dir($documentRoot) @@ -421,27 +427,27 @@ final public function init() : static && is_string($_SERVER['SCRIPT_FILENAME']??null) && is_file($_SERVER['SCRIPT_FILENAME']) ) { - $publicDir = dirname($_SERVER['SCRIPT_FILENAME']); + $publicDirectory = dirname($_SERVER['SCRIPT_FILENAME']); } else { - $publicDir = $root . DIRECTORY_SEPARATOR . 'public'; + $publicDirectory = $root . DIRECTORY_SEPARATOR . 'public'; } } } $defaultPaths = [ - 'controller' => $appDir . '/Controllers', - 'entity' => $appDir . '/Entities', - 'language' => $appDir . '/Languages', - 'middleware' => $appDir . '/Middlewares', - 'migration' => $appDir . '/Migrations', - 'module' => $appDir . '/Modules', - 'view' => $appDir . '/Views', - 'databaseEvent' => $appDir . '/DatabaseEvents', - 'scheduler' => $appDir . '/Schedulers', - 'command' => $appDir . '/Commands', + 'controller' => $appDirectory . '/Controllers', + 'entity' => $appDirectory . '/Entities', + 'language' => $appDirectory . '/Languages', + 'middleware' => $appDirectory . '/Middlewares', + 'migration' => $appDirectory . '/Migrations', + 'module' => $appDirectory . '/Modules', + 'view' => $appDirectory . '/Views', + 'databaseEvent' => $appDirectory . '/DatabaseEvents', + 'scheduler' => $appDirectory . '/Schedulers', + 'command' => $appDirectory . '/Commands', 'storage' => $root . '/storage', 'data' => $root . '/data', - 'public' => $publicDir, - 'upload' => $publicDir . '/uploads', + 'public' => $publicDirectory, + 'upload' => $publicDirectory . '/uploads', 'template' => 'templates', ]; @@ -547,16 +553,16 @@ final public function init() : static } } - $uploadDir = $path->get('upload'); - $uploadDir = $publicDir . DIRECTORY_SEPARATOR . $uploadDir; - $uploadDir = realpath($uploadDir)??$uploadDir; + $uploadDirectory = $path->get('upload'); + $uploadDirectory = $publicDirectory . DIRECTORY_SEPARATOR . $uploadDirectory; + $uploadDirectory = realpath($uploadDirectory)??$uploadDirectory; $uploadPath = realpath($path->get('upload'))??null; - if (!$uploadPath || !str_starts_with($uploadPath, $publicDir)) { - if ($uploadPath && is_dir($publicDir . DIRECTORY_SEPARATOR . $uploadPath)) { - $uploadDir = $publicDir . DIRECTORY_SEPARATOR . $uploadPath; - $uploadDir = realpath($uploadDir)??$uploadDir; + if (!$uploadPath || !str_starts_with($uploadPath, $publicDirectory)) { + if ($uploadPath && is_dir($publicDirectory . DIRECTORY_SEPARATOR . $uploadPath)) { + $uploadDirectory = $publicDirectory . DIRECTORY_SEPARATOR . $uploadPath; + $uploadDirectory = realpath($uploadDirectory)??$uploadDirectory; } - $path->set('upload', $uploadDir); + $path->set('upload', $uploadDirectory); } if ($this->getConfigError()) { @@ -566,11 +572,13 @@ final public function init() : static || $iniGet === '1' || $iniGet === 'true'; } - - $container->setParameter( - 'displayErrorDetails', - (bool)$environment['displayErrorDetails'] - ); + // debug also display error + $debug = $environment->get('debug') === true; + if ($debug) { + $environment['displayErrorDetails'] = true; + } + $environment['displayErrorDetails'] = (bool)$environment['displayErrorDetails']; + $container->setParameter('displayErrorDetails', $environment['displayErrorDetails']); if ($environment['displayErrorDetails']) { $manager?->attach( 'jsonResponder.debug', @@ -654,37 +662,37 @@ final public function init() : static ? $templatePath : 'templates'; $path->set('template', $templatePath); - $storageDir = $path->get('storage'); - $dataDir = $path->get('data'); + $storageDirectory = $path->get('storage'); + $dataDirectory = $path->get('data'); if (!Consolidation::isCli()) { - if (!is_dir($storageDir)) { - mkdir($storageDir, 0755, true); + if (!is_dir($storageDirectory)) { + mkdir($storageDirectory, 0755, true); } - if (!is_dir($dataDir)) { - mkdir($dataDir, 0755, true); + if (!is_dir($dataDirectory)) { + mkdir($dataDirectory, 0755, true); } } - $dataDir = realpath($dataDir)?:$dataDir; - $storageDir = realpath($storageDir)?:$storageDir; - $path->set('storage', $storageDir); - $path->set('data', $dataDir); + $dataDirectory = realpath($dataDirectory)?:$dataDirectory; + $storageDirectory = realpath($storageDirectory)?:$storageDirectory; + $path->set('storage', $storageDirectory); + $path->set('data', $dataDirectory); // directory parameter - $container->setParameter('viewsDir', $path->get('view')); - $container->setParameter('controllersDir', $path->get('controller')); - $container->setParameter('languagesDir', $path->get('language')); - $container->setParameter('migrationsDir', $path->get('migration')); - $container->setParameter('modulesDir', $path->get('module')); - $container->setParameter('entitiesDir', $path->get('entity')); - // $container->setParameter('repositoriesDir', $path->get('repository')); - $container->setParameter('databaseEventsDir', $path->get('databaseEvent')); - $container->setParameter('schedulersDir', $path->get('scheduler')); - $container->setParameter('commandsDir', $path->get('command')); - $container->setParameter('middlewaresDir', $path->get('middleware')); - $container->setParameter('storageDir', $storageDir); - $container->setParameter('uploadsDir', $uploadDir); - $container->setParameter('publicDir', $publicDir); - $container->setParameter('dataDir', $dataDir); + $container->setParameter('viewsDirectory', $path->get('view')); + $container->setParameter('controllersDirectory', $path->get('controller')); + $container->setParameter('languagesDirectory', $path->get('language')); + $container->setParameter('migrationsDirectory', $path->get('migration')); + $container->setParameter('modulesDirectory', $path->get('module')); + $container->setParameter('entitiesDirectory', $path->get('entity')); + // $container->setParameter('repositoriesDirectory', $path->get('repository')); + $container->setParameter('databaseEventsDirectory', $path->get('databaseEvent')); + $container->setParameter('schedulersDirectory', $path->get('scheduler')); + $container->setParameter('commandsDirectory', $path->get('command')); + $container->setParameter('middlewaresDirectory', $path->get('middleware')); + $container->setParameter('storageDirectory', $storageDirectory); + $container->setParameter('uploadsDirectory', $uploadDirectory); + $container->setParameter('publicDirectory', $publicDirectory); + $container->setParameter('dataDirectory', $dataDirectory); $container->setParameter('templatePath', $templatePath); // security @@ -842,7 +850,7 @@ protected function registerProviders(): void return; } $this->providerRegistered = true; - $container = $this->getHttpKernel()->getContainer(); + $container = $this->getContainer(); $manager = ContainerHelper::use(ManagerInterface::class, $container); $manager?->dispatch('kernel.beforeRegisterProviders', $this); try { @@ -853,7 +861,7 @@ protected function registerProviders(): void ? $translator : null; if ($translator) { - if ($container instanceof Container) { + if ($container instanceof SystemContainerInterface) { try { $poMoAdapter = $container->decorate(PoMoAdapter::class); $jsonAdapter = $container->decorate(JsonAdapter::class); @@ -865,14 +873,14 @@ protected function registerProviders(): void $translator->addAdapter($poMoAdapter); $translator->addAdapter($jsonAdapter); - $languageDir = $config?->get('language'); - if (is_string($languageDir) && is_dir($languageDir)) { + $languageDirectory = $config?->get('language'); + if (is_string($languageDirectory) && is_dir($languageDirectory)) { $poMoAdapter->registerDirectory( - $languageDir, + $languageDirectory, TranslatorInterface::DEFAULT_DOMAIN ); $jsonAdapter->registerDirectory( - $languageDir, + $languageDirectory, TranslatorInterface::DEFAULT_DOMAIN ); } diff --git a/src/HttpKernel/Helper/AbstractHelper.php b/src/HttpKernel/Helper/AbstractHelper.php index e20423d..d2119ec 100644 --- a/src/HttpKernel/Helper/AbstractHelper.php +++ b/src/HttpKernel/Helper/AbstractHelper.php @@ -10,10 +10,7 @@ use ArrayAccess\TrayDigita\HttpKernel\BaseKernel; use ArrayAccess\TrayDigita\Util\Filter\Consolidation; use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; -use ArrayAccess\TrayDigita\Util\Parser\PhpClassParserSerial; use DateInterval; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\Namespace_; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface; @@ -29,11 +26,14 @@ use function is_dir; use function is_string; use function md5; +use function php_strip_whitespace; +use function preg_match; use function spl_object_hash; use function sprintf; use function str_replace; use function str_starts_with; use function ucfirst; +use const PREG_UNMATCHED_AS_NULL; abstract class AbstractHelper { @@ -75,9 +75,14 @@ protected function generateCacheKey( */ final protected function getClasNameFromFile(SplFileInfo $splFileInfo) : ?string { - if (!$splFileInfo->isFile()) { + if (!$splFileInfo->isFile() + || !$splFileInfo->isReadable() + || $splFileInfo->getExtension() !== 'php' + || $splFileInfo->getSize() > 1048576 // 1MiB + ) { return null; } + $realPath = $splFileInfo->getRealPath(); $mtime = $splFileInfo->getMTime(); $cacheKey = $this->generateCacheKey("class:$realPath"); @@ -90,7 +95,63 @@ final protected function getClasNameFromFile(SplFileInfo $splFileInfo) : ?string || (!is_string($cacheItems['className']) && $cacheItems['className'] !== false) ) { try { - $parser = PhpClassParserSerial::fromFileInfo($splFileInfo); + $source = php_strip_whitespace($splFileInfo->getRealPath()); + // GETTING CLAS NAME + preg_match( + '~ + ^<\?php\s+ + # declare + (?: + (?:\s*declare\s*\(\s* + (?: + strict_types\s*=\s*[01] + |encoding\s*=\s*\'[^\']*\' + |ticks\s*=\s*[1-9][0-9]*(?:\.[0-9]*)? + ) + (?: + \s*,\s* + (?: + strict_types\s*=\s*[01] + |encoding\s*=\s*\'[^\']*\' + |ticks\s*=\s*[1-9][0-9]*(?:\.[0-9]*)? + ) + )* + \s* + \) + \s*;\s* + )* + )? + (?:\s*namespace\s+ + (? + [A-Z-a-z_\x80-\xff]+ + [A-Z-a-z_0-9\x80-\xff]* + (?:\\\[A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]*)* + )\s*; + )? + \s* + (use\s+[^;]+;\s*(?!class\s+).*)? + (?:(?:final|readonly)\s+)?class\s+ + (? + [A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]* + ) + [\s{] + ~xi', + $source, + $match, + PREG_UNMATCHED_AS_NULL + ); + $className = false; + $ns = null; + if (!empty($match)) { + $ns = ($match['namespace']??null?:null); + $className = $match['className']; + } + /* + // use parser + $parser = PhpClassParserSerial::fromSource($source); + unset($source); + $parser->getSource(); + echo microtime(true) * 1000 - $start."\n"; $className = false; $ns = null; foreach ($parser->getSource() as $stmt) { @@ -109,6 +170,7 @@ final protected function getClasNameFromFile(SplFileInfo $splFileInfo) : ?string break; } } + */ if ($className) { if ($ns) { $className = "$ns\\$className"; diff --git a/src/HttpKernel/Helper/KernelMiddlewareLoader.php b/src/HttpKernel/Helper/KernelMiddlewareLoader.php index e00fcdc..0876d65 100644 --- a/src/HttpKernel/Helper/KernelMiddlewareLoader.php +++ b/src/HttpKernel/Helper/KernelMiddlewareLoader.php @@ -14,10 +14,8 @@ use function class_exists; use function is_bool; use function is_dir; -use function ksort; use function trim; use function ucfirst; -use const SORT_ASC; class KernelMiddlewareLoader extends AbstractLoaderNameBased { @@ -74,53 +72,12 @@ protected function isProcessable(): bool /** * @var array> */ - private array $middlewares = []; + // private array $middlewares = []; - protected function postProcess(): void + /*protected function postProcess(): void { - foreach ($this->kernel->getHttpKernel()->getDeferredMiddlewares() as $priority => $middlewares) { - foreach ($middlewares as $middleware) { - $this->middlewares[$priority][] = $middleware; - } - } - // clear - $this->kernel->getHttpKernel()->clearDeferredMiddlewares(); - ksort($this->middlewares, SORT_ASC); - $manager = $this->getManager(); - foreach ($this->middlewares as $priority => $middlewares) { - unset($this->middlewares[$priority]); - foreach ($middlewares as $middleware) { - try { - // @dispatch(kernel.beforeRegisterMiddleware) - $manager?->dispatch( - 'kernel.beforeRegisterMiddleware', - $this->kernel->getHttpKernel(), - $this->kernel, - $this, - $middleware - ); - $this->kernel->getHttpKernel()->addMiddleware($middleware); - // @dispatch(kernel.registerMiddleware) - $manager?->dispatch( - 'kernel.registerMiddleware', - $this->kernel->getHttpKernel(), - $this->kernel, - $this, - $middleware - ); - } finally { - // @dispatch(kernel.afterRegisterMiddleware) - $manager?->dispatch( - 'kernel.afterRegisterMiddleware', - $this->kernel->getHttpKernel(), - $this->kernel, - $this, - $middleware - ); - } - } - } - } + $this->kernel->getHttpKernel()->dispatchDeferredMiddleware(); + }*/ protected function doRegister(): bool { @@ -172,13 +129,14 @@ protected function loadService( if (!$splFileInfo->isFile()) { return; } + $httpKernel = $this->kernel->getHttpKernel(); $realPath = $splFileInfo->getRealPath(); $manager = $this->getManager(); // @dispatch(kernel.beforeLoadMiddleware) $manager?->dispatch( 'kernel.beforeLoadMiddleware', $realPath, - $this->kernel->getHttpKernel(), + $httpKernel, $this->kernel, $this ); @@ -190,7 +148,7 @@ protected function loadService( $manager?->dispatch( 'kernel.loadMiddleware', $realPath, - $this->kernel->getHttpKernel(), + $httpKernel, $this->kernel, $this ); @@ -215,7 +173,7 @@ protected function loadService( ) { $result = ContainerHelper::resolveCallable($className, $this->getContainer()); if ($result instanceof AbstractMiddleware) { - $this->middlewares[$result->getPriority()][] = $result; + $httpKernel->addDeferredMiddleware($result); } } } catch (Throwable $e) { @@ -233,7 +191,7 @@ protected function loadService( $manager?->dispatch( 'kernel.loadMiddleware', $realPath, - $this->kernel->getHttpKernel(), + $httpKernel, $this->kernel, $this, $result @@ -246,7 +204,7 @@ protected function loadService( $manager?->dispatch( 'kernel.afterLoadMiddleware', $realPath, - $this->kernel->getHttpKernel(), + $httpKernel, $this->kernel, $this, $result diff --git a/src/HttpKernel/Interfaces/HttpKernelInterface.php b/src/HttpKernel/Interfaces/HttpKernelInterface.php index 5ea0233..0be8018 100644 --- a/src/HttpKernel/Interfaces/HttpKernelInterface.php +++ b/src/HttpKernel/Interfaces/HttpKernelInterface.php @@ -7,6 +7,7 @@ use ArrayAccess\TrayDigita\Event\Interfaces\ManagerIndicateInterface; use ArrayAccess\TrayDigita\Handler\Interfaces\MiddlewareDispatcherInterface; use ArrayAccess\TrayDigita\Http\Interfaces\ResponseDispatcherInterface; +use ArrayAccess\TrayDigita\Kernel\Interfaces\KernelInterface; use ArrayAccess\TrayDigita\Routing\Interfaces\RouterInterface; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; @@ -24,6 +25,7 @@ interface HttpKernelInterface extends ManagerIndicateInterface, ContainerIndicateInterface { + public function getKernel() : KernelInterface; public function getStartMemory() : int; @@ -45,6 +47,11 @@ public function addDeferredMiddleware(MiddlewareInterface $middleware); * @return array> */ public function getDeferredMiddlewares() : array; - + + public function dispatchDeferredMiddleware(); + + /** + * Clear the deferred middleware + */ public function clearDeferredMiddlewares(); } diff --git a/src/Kernel/AbstractHttpKernel.php b/src/Kernel/AbstractHttpKernel.php new file mode 100644 index 0000000..aa65a7b --- /dev/null +++ b/src/Kernel/AbstractHttpKernel.php @@ -0,0 +1,410 @@ +startTime = microtime(true); + $this->startMemory = memory_get_usage(); + + $container ??= (new ContainerFactory())->createDefault(); + $this->container = $container; + $manager ??= ContainerHelper::use(ManagerInterface::class, $container); + if (!$manager) { + $container->remove(ManagerInterface::class); + $manager = new Manager(); + $container->set(ManagerInterface::class, $manager); + } + $this->managerObject = $manager; + $this->container->set(HttpKernelInterface::class, fn () => $this); + + // router + $definitions = [ + RouterInterface::class => Router::class, + MiddlewareDispatcherInterface::class => MiddlewareDispatcher::class, + RouteRunnerInterface::class => RouteRunner::class, + ]; + + foreach ($definitions as $key => $item) { + try { + if (!$container->has($key)) { + $container->set($key, $item); + } + } catch (Throwable) { + } + } + + $this->router = ContainerHelper::service(RouterInterface::class, $container); + $this->middlewareDispatcher = ContainerHelper::service(MiddlewareDispatcherInterface::class, $container); + } + + public function getKernel(): KernelInterface + { + return $this->kernel; + } + + public function getStartMemory(): int + { + return $this->startMemory; + } + + public function getStartTime(): float + { + return $this->startTime; + } + + public function getContainer(): ContainerInterface + { + return $this->container; + } + + public function getRouter(): RouterInterface + { + return $this->router; + } + + public function getMiddlewareDispatcher(): MiddlewareDispatcherInterface + { + return $this->middlewareDispatcher; + } + + public function getManager(): ManagerInterface + { + return $this->managerObject; + } + + public function addMiddleware(MiddlewareInterface $middleware): static + { + $this->getMiddlewareDispatcher()->addMiddleware($middleware); + return $this; + } + + public function addDeferredMiddleware(MiddlewareInterface $middleware): static + { + $priority = $middleware instanceof AbstractMiddleware + ? $middleware->getPriority() + : 10; + $this->deferredMiddlewares[$priority][] = $middleware; + return $this; + } + + public function getDeferredMiddlewares() : array + { + return $this->deferredMiddlewares; + } + + public function clearDeferredMiddlewares(): void + { + $this->deferredMiddlewares = []; + } + + public function getLastResponse(): ?ResponseInterface + { + return $this->lastResponse; + } + + public function getLastRequest(): ?ServerRequestInterface + { + return $this->lastRequest; + } + + public function dispatchDeferredMiddleware(): void + { + if (empty($this->deferredMiddlewares)) { + return; + } + + ksort($this->deferredMiddlewares, SORT_ASC); + $manager = $this->getManager(); + foreach ($this->deferredMiddlewares as $priority => $middlewares) { + unset($this->deferredMiddlewares[$priority]); + foreach ($middlewares as $middleware) { + try { + // @dispatch(kernel.beforeRegisterMiddleware) + $manager->dispatch( + 'kernel.beforeRegisterMiddleware', + $this, + $this->kernel, + $this, + $middleware + ); + $this->addMiddleware($middleware); + // @dispatch(kernel.registerMiddleware) + $manager->dispatch( + 'kernel.registerMiddleware', + $this, + $this->kernel, + $this, + $middleware + ); + } finally { + // @dispatch(kernel.afterRegisterMiddleware) + $manager->dispatch( + 'kernel.afterRegisterMiddleware', + $this, + $this->kernel, + $this, + $middleware + ); + } + } + } + } + + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $this->assertCallstack(); + if ($this->lastResponse + && $this->lastRequest + && spl_object_hash($request) === spl_object_hash($this->lastRequest) + ) { + $this->resetCallstack(); + return $this->lastResponse; + } + + $manager = $this->getManager(); + $manager->dispatch('httpKernel.beforeHandle', $this); + // attach the middleware + $this->dispatchDeferredMiddleware(); + try { + $this->lastRequest = $request; + // add middleware + $this->lastResponse = $this->getMiddlewareDispatcher()->handle($request); + /** + * This is to be in compliance with RFC 2616, Section 9. + * If the incoming request method is HEAD, we need to ensure that the response body + * is empty as the request may fall back on a GET route handler due to FastRoute's + * routing logic which could potentially append content to the response body + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 + */ + $method = strtoupper($request->getMethod()); + if ($method === 'HEAD' + // also empty on cli + || $method === 'CLI' + ) { + $emptyBody = $this->getResponseFactory()->createResponse()->getBody(); + $this->lastResponse = $this->lastResponse->withBody($emptyBody); + } + $manager->dispatch( + 'httpKernel.handle', + $this, + $this->lastResponse + ); + $this->resetCallstack(); + return $this->lastResponse; + } finally { + $manager->dispatch( + 'httpKernel.afterHandle', + $this + ); + } + } + + public function dispatchResponse(ResponseInterface $response): ResponseInterface + { + $this->assertCallstack(); + + $manager = $this->getManager(); + // @dispatch(httpKernel.beforeDispatch) + $manager->dispatch( + 'httpKernel.beforeDispatch', + $this, + $response + ); + + /** + * @var SystemContainerInterface $container + */ + $container = $this->getContainer(); + $doEmit = ! Consolidation::isCli() || $manager->dispatch( + 'httpKernel.emitResponse', + false + ) === true; + $emitter = ContainerHelper::service(ResponseEmitterInterface::class, $container) + ?? new ResponseEmitter(); + if ($emitter->isClosed()) { + throw new UnProcessableException( + 'Emitter has been closed.' + ); + } + + // @dispatch(httpKernel.dispatch) + $newResponse = $manager->dispatch('httpKernel.dispatch', $response); + if ($newResponse instanceof ResponseInterface) { + $response = $newResponse; + } + + // @dispatch(response.final) + $newResponse = $manager->dispatch('response.final', $response); + if ($newResponse instanceof ResponseInterface) { + $response = $newResponse; + } + + // @dispatch(httpKernel.afterDispatch) + $manager->dispatch( + 'httpKernel.afterDispatch', + $this, + $response + ); + + if ($doEmit) { + $reduceError = (bool) $manager + ->dispatch('response.reduceError', true); + $sendPreviousBuffer = (bool) $manager + ->dispatch('response.sendPreviousBuffer', true); + $emitter->emit($response, $reduceError, $sendPreviousBuffer); + } + + // @dispatch(httpKernel.afterDispatch) + $manager->dispatch( + 'httpKernel.dispatched', + $this, + $response + ); + + $this->resetCallstack(); + + return $response; + } + + public function run(ServerRequestInterface $request) : ResponseInterface + { + return $this->dispatchResponse($this->handle($request)); + } + + public function terminate(ServerRequestInterface $request, ResponseInterface $response): void + { + $this->assertCallstack(); + $manager = $this->getManager(); + // @dispatch(httpKernel.terminate) + $manager + ->dispatch( + 'httpKernel.beforeTerminate', + $this, + $request, + $response + ); + try { + $manager->dispatch( + 'httpKernel.terminate', + $this, + $request, + $response + ); + /* + * @var SystemContainerInterface $container + $container = $this->getContainer(); + if (!$container->has(ResponseEmitterInterface::class)) { + return; + } + try { + $emitter = $container->get(ResponseEmitterInterface::class); + !$emitter->isClosed() && $emitter->close(); + } catch (Throwable) { + }*/ + } finally { + $manager->dispatch( + 'httpKernel.afterTerminate', + $this, + $request, + $response + ); + $this->resetCallstack(); + } + } + + public function __call(string $name, array $arguments) + { + return $this->getRouter()->$name(...$arguments); + } + + public function __debugInfo(): ?array + { + return Consolidation::debugInfo( + $this, + excludeKeys: [ + 'router', + 'lastResponse', + 'lastRequest' + ] + ); + } +} diff --git a/src/Kernel/AbstractKernel.php b/src/Kernel/AbstractKernel.php index 6a0097c..aa16d1c 100644 --- a/src/Kernel/AbstractKernel.php +++ b/src/Kernel/AbstractKernel.php @@ -4,30 +4,48 @@ namespace ArrayAccess\TrayDigita\Kernel; use ArrayAccess\TrayDigita\Container\Factory\ContainerFactory; +use ArrayAccess\TrayDigita\Container\Interfaces\ContainerFactoryInterface; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; +use ArrayAccess\TrayDigita\Exceptions\Runtime\RuntimeException; use ArrayAccess\TrayDigita\HttpKernel\BaseKernel; use ArrayAccess\TrayDigita\HttpKernel\Interfaces\HttpKernelInterface; use ArrayAccess\TrayDigita\Kernel\Interfaces\KernelInterface; -use Throwable; +use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; abstract class AbstractKernel extends BaseKernel { - public readonly ContainerFactory $containerFactory; + public readonly ContainerFactoryInterface $containerFactory; public function __construct( - ?ContainerFactory $containerFactory = null, + ?ContainerFactoryInterface $containerFactory = null, ?string $baseConfigFileName = KernelInterface::BASE_CONFIG_FILE_NAME ) { $containerFactory ??= new ContainerFactory(); $this->containerFactory = $containerFactory; $container = $containerFactory->createDefault(); - if (!$container->has(HttpKernelInterface::class)) { - $container->set(HttpKernelInterface::class, HttpKernel::class); + if (!($hasRaw = $container->hasRawService(KernelInterface::class)) + || $container->getRawService(KernelInterface::class) !== $this + ) { + if ($hasRaw) { + $container->remove(KernelInterface::class); + } + $container->raw(KernelInterface::class, $this); } - try { - $kernel = $container->get(HttpKernelInterface::class); - parent::__construct($kernel, $baseConfigFileName); - } catch (Throwable) { - parent::__construct(new HttpKernel($container)); + + $kernel = ContainerHelper::getNull( + HttpKernelInterface::class, + $container + )??new HttpKernel($this, $container, ContainerHelper::getNull( + ManagerInterface::class, + $container + )); + + // kernel should equal this + if ($kernel->getKernel() !== $this) { + throw new RuntimeException( + 'Kernel instance could not contain outside current object' + ); } + parent::__construct($kernel, $baseConfigFileName); } } diff --git a/src/Kernel/Decorator.php b/src/Kernel/Decorator.php index f379c10..2eaccdd 100644 --- a/src/Kernel/Decorator.php +++ b/src/Kernel/Decorator.php @@ -7,9 +7,9 @@ use ArrayAccess\TrayDigita\Auth\Roles\Interfaces\PermissionInterface; use ArrayAccess\TrayDigita\Benchmark\Interfaces\ProfilerInterface; use ArrayAccess\TrayDigita\Collection\Config; -use ArrayAccess\TrayDigita\Container\Container; use ArrayAccess\TrayDigita\Container\ContainerWrapper; use ArrayAccess\TrayDigita\Container\Factory\ContainerFactory; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Database\Connection; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Exceptions\InvalidArgument\EmptyArgumentException; @@ -267,7 +267,7 @@ public static function service(string $name) return self::resolveDepend($name); } - private static function resolveInternal(string $name): Container|ContainerWrapper + private static function resolveInternal(string $name): SystemContainerInterface { $container = self::container(); $container = ContainerWrapper::maybeContainerOrCreate($container); diff --git a/src/Kernel/HttpKernel.php b/src/Kernel/HttpKernel.php index 10899d2..2af123f 100644 --- a/src/Kernel/HttpKernel.php +++ b/src/Kernel/HttpKernel.php @@ -3,359 +3,6 @@ namespace ArrayAccess\TrayDigita\Kernel; -use ArrayAccess\TrayDigita\Container\Container; -use ArrayAccess\TrayDigita\Container\Factory\ContainerFactory; -use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; -use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; -use ArrayAccess\TrayDigita\Event\Manager; -use ArrayAccess\TrayDigita\Exceptions\Runtime\UnProcessableException; -use ArrayAccess\TrayDigita\Handler\Interfaces\MiddlewareDispatcherInterface; -use ArrayAccess\TrayDigita\Http\Interfaces\ResponseEmitterInterface; -use ArrayAccess\TrayDigita\Http\ResponseEmitter; -use ArrayAccess\TrayDigita\HttpKernel\Interfaces\HttpKernelInterface; -use ArrayAccess\TrayDigita\HttpKernel\Interfaces\TerminableInterface; -use ArrayAccess\TrayDigita\Middleware\AbstractMiddleware; -use ArrayAccess\TrayDigita\Middleware\MiddlewareDispatcher; -use ArrayAccess\TrayDigita\Routing\Interfaces\RouterInterface; -use ArrayAccess\TrayDigita\Routing\Interfaces\RouteRunnerInterface; -use ArrayAccess\TrayDigita\Routing\Router; -use ArrayAccess\TrayDigita\Routing\RouteRunner; -use ArrayAccess\TrayDigita\Traits\Http\ResponseFactoryTrait; -use ArrayAccess\TrayDigita\Traits\Manager\ManagerAllocatorTrait; -use ArrayAccess\TrayDigita\Traits\Service\CallStackTraceTrait; -use ArrayAccess\TrayDigita\Util\Filter\Consolidation; -use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; -use Psr\Container\ContainerInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Throwable; -use function memory_get_usage; -use function microtime; -use function spl_object_hash; -use function strtoupper; - -/** - * @mixin RouterInterface - */ -class HttpKernel implements - HttpKernelInterface, - TerminableInterface, - ManagerAllocatorInterface +class HttpKernel extends AbstractHttpKernel { - use ResponseFactoryTrait, - ManagerAllocatorTrait, - CallStackTraceTrait; - - private MiddlewareDispatcherInterface $middlewareDispatcher; - - private ContainerInterface $container; - - private RouterInterface $router; - - private ?ResponseInterface $lastResponse = null; - - private ?ServerRequestInterface $lastRequest = null; - - /** - * Start memory @uses memory_get_usage() - * @var int - */ - private int $startMemory; - - /** - * Start time @uses microtime(true) - * @var float - */ - private float $startTime; - - /** - * @var array - */ - private array $deferredMiddlewares = []; - - public function __construct( - ContainerInterface $container = null, - ManagerInterface $manager = null - ) { - $this->startTime = microtime(true); - $this->startMemory = memory_get_usage(); - - $container ??= (new ContainerFactory())->createDefault(); - $this->container = $container; - $manager ??= ContainerHelper::use(ManagerInterface::class, $container); - if (!$manager) { - $container->remove(ManagerInterface::class); - $manager = new Manager(); - $container->set(ManagerInterface::class, $manager); - } - $this->managerObject = $manager; - $this->container->set(HttpKernelInterface::class, fn () => $this); - - // router - $definitions = [ - RouterInterface::class => Router::class, - MiddlewareDispatcherInterface::class => MiddlewareDispatcher::class, - RouteRunnerInterface::class => RouteRunner::class, - ]; - - foreach ($definitions as $key => $item) { - try { - if (!$container->has($key)) { - $container->set($key, $item); - } - } catch (Throwable) { - } - } - - $this->router = ContainerHelper::service(RouterInterface::class, $container); - $this->middlewareDispatcher = ContainerHelper::service(MiddlewareDispatcherInterface::class, $container); - } - - public function getStartMemory(): int - { - return $this->startMemory; - } - - public function getStartTime(): float - { - return $this->startTime; - } - - public function getContainer(): ContainerInterface - { - return $this->container; - } - - public function getRouter(): RouterInterface - { - return $this->router; - } - - public function getMiddlewareDispatcher(): MiddlewareDispatcherInterface - { - return $this->middlewareDispatcher; - } - - public function getManager(): ManagerInterface - { - return $this->managerObject; - } - - public function addMiddleware(MiddlewareInterface $middleware): static - { - $this->getMiddlewareDispatcher()->addMiddleware($middleware); - return $this; - } - - public function addDeferredMiddleware(MiddlewareInterface $middleware): static - { - $priority = $middleware instanceof AbstractMiddleware - ? $middleware->getPriority() - : 10; - $this->deferredMiddlewares[$priority][] = $middleware; - return $this; - } - - public function getDeferredMiddlewares() : array - { - return $this->deferredMiddlewares; - } - - public function clearDeferredMiddlewares(): void - { - $this->deferredMiddlewares = []; - } - - public function getLastResponse(): ?ResponseInterface - { - return $this->lastResponse; - } - - public function getLastRequest(): ?ServerRequestInterface - { - return $this->lastRequest; - } - - public function handle(ServerRequestInterface $request) : ResponseInterface - { - $this->assertCallstack(); - if ($this->lastResponse - && $this->lastRequest - && spl_object_hash($request) === spl_object_hash($this->lastRequest) - ) { - $this->resetCallstack(); - return $this->lastResponse; - } - - $manager = $this->getManager(); - $manager->dispatch( - 'httpKernel.beforeHandle', - $this - ); - - try { - $this->lastRequest = $request; - // add middleware - $this->lastResponse = $this->getMiddlewareDispatcher()->handle($request); - - /** - * This is to be in compliance with RFC 2616, Section 9. - * If the incoming request method is HEAD, we need to ensure that the response body - * is empty as the request may fall back on a GET route handler due to FastRoute's - * routing logic which could potentially append content to the response body - * https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 - */ - $method = strtoupper($request->getMethod()); - if ($method === 'HEAD' - // also empty on cli - || $method === 'CLI' - ) { - $emptyBody = $this->getResponseFactory()->createResponse()->getBody(); - $this->lastResponse = $this->lastResponse->withBody($emptyBody); - } - $manager->dispatch( - 'httpKernel.handle', - $this, - $this->lastResponse - ); - $this->resetCallstack(); - return $this->lastResponse; - } finally { - $manager->dispatch( - 'httpKernel.afterHandle', - $this - ); - } - } - - public function dispatchResponse(ResponseInterface $response): ResponseInterface - { - $this->assertCallstack(); - - $manager = $this->getManager(); - // @dispatch(httpKernel.beforeDispatch) - $manager->dispatch( - 'httpKernel.beforeDispatch', - $this, - $response - ); - - /** - * @var Container $container - */ - $container = $this->getContainer(); - $doEmit = ! Consolidation::isCli() || $manager->dispatch( - 'httpKernel.emitResponse', - false - ) === true; - $emitter = ContainerHelper::service(ResponseEmitterInterface::class, $container) - ?? new ResponseEmitter(); - if ($emitter->isClosed()) { - throw new UnProcessableException( - 'Emitter has been closed.' - ); - } - - // @dispatch(httpKernel.dispatch) - $newResponse = $manager->dispatch('httpKernel.dispatch', $response); - if ($newResponse instanceof ResponseInterface) { - $response = $newResponse; - } - - // @dispatch(response.final) - $newResponse = $manager->dispatch('response.final', $response); - if ($newResponse instanceof ResponseInterface) { - $response = $newResponse; - } - - // @dispatch(httpKernel.afterDispatch) - $manager->dispatch( - 'httpKernel.afterDispatch', - $this, - $response - ); - - if ($doEmit) { - $reduceError = (bool) $manager - ->dispatch('response.reduceError', true); - $sendPreviousBuffer = (bool) $manager - ->dispatch('response.sendPreviousBuffer', true); - $emitter->emit($response, $reduceError, $sendPreviousBuffer); - } - - // @dispatch(httpKernel.afterDispatch) - $manager->dispatch( - 'httpKernel.dispatched', - $this, - $response - ); - - $this->resetCallstack(); - - return $response; - } - - public function run(ServerRequestInterface $request) : ResponseInterface - { - return $this->dispatchResponse($this->handle($request)); - } - - public function terminate(ServerRequestInterface $request, ResponseInterface $response): void - { - $this->assertCallstack(); - $manager = $this->getManager(); - // @dispatch(httpKernel.terminate) - $manager - ->dispatch( - 'httpKernel.beforeTerminate', - $this, - $request, - $response - ); - try { - $manager->dispatch( - 'httpKernel.terminate', - $this, - $request, - $response - ); - /* - * @var Container $container - $container = $this->getContainer(); - if (!$container->has(ResponseEmitterInterface::class)) { - return; - } - try { - $emitter = $container->get(ResponseEmitterInterface::class); - !$emitter->isClosed() && $emitter->close(); - } catch (Throwable) { - }*/ - } finally { - $manager->dispatch( - 'httpKernel.afterTerminate', - $this, - $request, - $response - ); - $this->resetCallstack(); - } - } - - public function __call(string $name, array $arguments) - { - return $this->getRouter()->$name(...$arguments); - } - - public function __debugInfo(): ?array - { - return Consolidation::debugInfo( - $this, - excludeKeys: [ - 'router', - 'lastResponse', - 'lastRequest' - ] - ); - } } diff --git a/src/Lang/default.pot b/src/Lang/default.pot index f9cf6cb..eeb0c89 100644 --- a/src/Lang/default.pot +++ b/src/Lang/default.pot @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: TrayDigita 1.0.0\n" -"POT-Creation-Date: 2023-10-21 03:07+0700\n" +"POT-Creation-Date: 2023-10-22 02:40+0700\n" "PO-Revision-Date: 2023-09-24 19:00+0700\n" "Last-Translator: ArrayAccess\n" "Language-Team: ArrayAccess\n" @@ -1905,7 +1905,7 @@ msgctxt "chunk-uploader" msgid "Directory %s is not writable" msgstr "" -#: Uploader/ChunkHandler.php:119 Uploader/ChunkProcessor.php:223 +#: Uploader/ChunkHandler.php:118 Uploader/ChunkProcessor.php:223 #, php-format msgctxt "chunk-uploader" msgid "Request id \"%s\" is not valid" @@ -1916,44 +1916,44 @@ msgctxt "chunk-uploader" msgid "Cache upload storage is not writable." msgstr "" -#: Uploader/ChunkHandler.php:280 +#: Uploader/ChunkHandler.php:281 msgctxt "chunk-uploader" msgid "Upload cache file is not writable." msgstr "" -#: Uploader/ChunkHandler.php:294 +#: Uploader/ChunkHandler.php:295 msgctxt "chunk-uploader" msgid "Can not create cached stream." msgstr "" -#: Uploader/ChunkHandler.php:305 +#: Uploader/ChunkHandler.php:306 msgctxt "chunk-uploader" msgid "Cache file has been locked." msgstr "" -#: Uploader/ChunkHandler.php:417 +#: Uploader/ChunkHandler.php:420 msgctxt "chunk-uploader" msgid "Offset upload position is invalid." msgstr "" -#: Uploader/ChunkHandler.php:451 +#: Uploader/ChunkHandler.php:454 msgctxt "chunk-uploader" msgid "Source uploaded file does not exist." msgstr "" -#: Uploader/ChunkHandler.php:471 +#: Uploader/ChunkHandler.php:474 #, php-format msgctxt "chunk-uploader" msgid "Upload cache file is not ready to move : (%d)." msgstr "" -#: Uploader/ChunkHandler.php:520 +#: Uploader/ChunkHandler.php:523 #, php-format msgctxt "chunk-uploader" msgid "Target file \"%s\" is not writable." msgstr "" -#: Uploader/ChunkHandler.php:540 +#: Uploader/ChunkHandler.php:543 #, php-format msgctxt "chunk-uploader" msgid "Target directory \"%s\" is not writable." diff --git a/src/Lang/id.po b/src/Lang/id.po index a30642b..0219a0f 100644 --- a/src/Lang/id.po +++ b/src/Lang/id.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: TrayDigita 1.0.0\n" -"POT-Creation-Date: 2023-10-21 03:07+0700\n" -"PO-Revision-Date: 2023-10-21 03:07+0700\n" +"POT-Creation-Date: 2023-10-22 02:40+0700\n" +"PO-Revision-Date: 2023-10-22 02:40+0700\n" "Last-Translator: \n" "Language-Team: ArrayAccess\n" "Language: id\n" @@ -1973,7 +1973,7 @@ msgctxt "chunk-uploader" msgid "Directory %s is not writable" msgstr "Direktori %s tidak dapat ditulisi" -#: Uploader/ChunkHandler.php:119 Uploader/ChunkProcessor.php:223 +#: Uploader/ChunkHandler.php:118 Uploader/ChunkProcessor.php:223 #, php-format msgctxt "chunk-uploader" msgid "Request id \"%s\" is not valid" @@ -1984,44 +1984,44 @@ msgctxt "chunk-uploader" msgid "Cache upload storage is not writable." msgstr "Direktori penyimpanan cache tidak dapat ditulisi." -#: Uploader/ChunkHandler.php:280 +#: Uploader/ChunkHandler.php:281 msgctxt "chunk-uploader" msgid "Upload cache file is not writable." msgstr "Berkas cache unggahan tidak dapat ditulisi." -#: Uploader/ChunkHandler.php:294 +#: Uploader/ChunkHandler.php:295 msgctxt "chunk-uploader" msgid "Can not create cached stream." msgstr "Tidak dapat membuat cached stream." -#: Uploader/ChunkHandler.php:305 +#: Uploader/ChunkHandler.php:306 msgctxt "chunk-uploader" msgid "Cache file has been locked." msgstr "Berkas cache terkunci." -#: Uploader/ChunkHandler.php:417 +#: Uploader/ChunkHandler.php:420 msgctxt "chunk-uploader" msgid "Offset upload position is invalid." msgstr "Posisi offset unggahan tidak sah." -#: Uploader/ChunkHandler.php:451 +#: Uploader/ChunkHandler.php:454 msgctxt "chunk-uploader" msgid "Source uploaded file does not exist." msgstr "Berkas sumber yang diunggah tidak ada." -#: Uploader/ChunkHandler.php:471 +#: Uploader/ChunkHandler.php:474 #, php-format msgctxt "chunk-uploader" msgid "Upload cache file is not ready to move : (%d)." msgstr "Berkas cache yang diunggah belum siap dipindahkan : (%d)." -#: Uploader/ChunkHandler.php:520 +#: Uploader/ChunkHandler.php:523 #, php-format msgctxt "chunk-uploader" msgid "Target file \"%s\" is not writable." msgstr "Berkas tujuan \"%s\" tidak dapat ditulisi." -#: Uploader/ChunkHandler.php:540 +#: Uploader/ChunkHandler.php:543 #, php-format msgctxt "chunk-uploader" msgid "Target directory \"%s\" is not writable." diff --git a/src/Middleware/AbstractMiddleware.php b/src/Middleware/AbstractMiddleware.php index efba074..506d390 100644 --- a/src/Middleware/AbstractMiddleware.php +++ b/src/Middleware/AbstractMiddleware.php @@ -3,8 +3,8 @@ namespace ArrayAccess\TrayDigita\Middleware; -use ArrayAccess\TrayDigita\Container\Container; use ArrayAccess\TrayDigita\Container\ContainerWrapper; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerIndicateInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; @@ -51,9 +51,9 @@ public function getManager(): ?ManagerInterface } /** - * @return ContainerInterface|Container + * @return ContainerInterface|SystemContainerInterface */ - public function getContainer(): ContainerInterface|Container + public function getContainer(): ContainerInterface|SystemContainerInterface { return $this->container; } diff --git a/src/PossibleRoot.php b/src/PossibleRoot.php index cd0b69d..eade98e 100644 --- a/src/PossibleRoot.php +++ b/src/PossibleRoot.php @@ -60,9 +60,9 @@ public static function getPossibleRootDirectory() $config = json_decode(file_get_contents($composerJson), true); $config = is_array($config) ? $config : []; $config = isset($config['config']) ? $config['config'] : []; - $vendorDir = isset($config['vendor-dir']) ? $config['vendor-dir'] : null; - if ($vendorDir && file_exists("$root/$vendorDir/autoload.php")) { - require "$root/$vendorDir/autoload.php"; + $vendorDirectory = isset($config['vendor-dir']) ? $config['vendor-dir'] : null; + if ($vendorDirectory && file_exists("$root/$vendorDirectory/autoload.php")) { + require "$root/$vendorDirectory/autoload.php"; } } } @@ -119,8 +119,8 @@ public static function getPossibleRootDirectory() $config = is_array($config) ? $config : []; $config = isset($config['config']) ? $config['config'] : []; } - $vendorDir = isset($config['vendor-dir']) ? $config['vendor-dir'] : null; - if (!is_string($vendorDir) || ! is_dir("$root/$vendorDir")) { + $vendorDirectory = isset($config['vendor-dir']) ? $config['vendor-dir'] : null; + if (!is_string($vendorDirectory) || ! is_dir("$root/$vendorDirectory")) { $root = dirname(TD_APP_DIRECTORY); } self::$composerJsonConfig[$composerJson] = $root; diff --git a/src/Responder/FileResponder.php b/src/Responder/FileResponder.php index 8c63d78..b5f9590 100644 --- a/src/Responder/FileResponder.php +++ b/src/Responder/FileResponder.php @@ -56,7 +56,7 @@ class FileResponder implements FileResponderInterface const DEFAULT_MIMETYPE = 'application/octet-stream'; - protected string $fileName; + protected string $attachmentFileName; protected int $size; @@ -66,6 +66,8 @@ class FileResponder implements FileResponderInterface protected bool $sendRealMimeType = true; + protected bool $sendContentLength = true; + protected bool $allowRange = true; protected int $maxRanges = 100; @@ -92,7 +94,7 @@ public function __construct(SplFileInfo|string $file) $file = new SplFileInfo($file); } $this->file = $file; - $this->fileName = $this->file->getBasename(); + $this->attachmentFileName = $this->file->getBasename(); $this->size = $this->valid() ? $this->file->getSize() : 0; } @@ -136,14 +138,29 @@ public function isSendAsAttachment(): bool return $this->sendAsAttachment; } - public function setFileName(string $fileName): void + public function isSendContentLength(): bool { - $this->fileName = $fileName; + return $this->sendContentLength; + } + + public function sendContentLength(bool $enable): void + { + $this->sendContentLength = $enable; + } + + public function setAttachmentFileName(string $fileName): void + { + $this->attachmentFileName = $fileName; + } + + public function getAttachmentFileName(): string + { + return $this->attachmentFileName; } public function resetFileName(): void { - $this->fileName = $this->getFile()->getBasename(); + $this->attachmentFileName = $this->getFile()->getBasename(); } public function sendRealMimeType(bool $enable): void @@ -174,14 +191,6 @@ public function getBoundary(): string return $this->boundary ??= md5(RandomString::bytes(16)); } - public function sendLastModified(): bool - { - return $this->sendHeader( - 'Last-Modified', - gmdate('Y-m-d H:i:s \G\M\T') - ); - } - /** * @return ?string */ @@ -202,42 +211,6 @@ public function getEtag(): ?string return $this->eTag = sprintf('%x-%x', $time, $size); } - public function send(?ServerRequestInterface $request = null): never - { - $request ??= ServerRequest::fromGlobals( - Decorator::service(ServerRequestFactoryInterface::class), - Decorator::service(StreamFactoryInterface::class), - ); - $method = strtoupper($request->getMethod()); - if (!in_array($method, self::ALLOWED_METHODS)) { - $exceptions = new MethodNotAllowedException( - $request - ); - $exceptions->setAllowedMethods(self::ALLOWED_METHODS); - throw $exceptions; - } - if (!$this->valid()) { - throw new SourceFileFailException( - $this->file->getPathname(), - sprintf( - 'File %s is not valid', - $this->file - ) - ); - } - // remove all buffer - $count = 5; - while (--$count > 0 && ob_get_level() > 0) { - ob_end_clean(); - } - if (headers_sent()) { - throw new HttpRuntimeException( - 'Header already sent' - ); - } - $this->sendData($request); - } - /** * @param string $name * @param string|int|float $value @@ -267,12 +240,24 @@ private function sendHeader( return true; } + public function sendHeaderLastModified(): bool + { + if (!$this->isSendLastModifiedTime()) { + return false; + } + + return $this->sendHeader( + 'Last-Modified', + gmdate('Y-m-d H:i:s \G\M\T') + ); + } + /** * @param string|array|null $cacheType * @param int|null $maxAge * @return bool */ - public function sendCacheHeader( + public function sendHeaderCache( string|array|null $cacheType = null, ?int $maxAge = null ) : bool { @@ -296,7 +281,7 @@ public function sendCacheHeader( * * @return bool */ - public function sendAcceptRanges(): bool + public function sendHeaderAcceptRanges(): bool { return $this->sendHeader( 'Accept-Ranges', @@ -306,50 +291,116 @@ public function sendAcceptRanges(): bool ); } - public function sendContentLength(int $length): bool + public function sendHeaderContentLength(int $length): bool { + if (!$this->isSendContentLength()) { + return false; + } return $this->sendHeader('Content-Length', $length); } - public function sendEtag(): bool + public function sendHeaderEtag(): bool { $etag = $this->getEtag(); return $etag && $this->sendHeader('Etag', $etag); } - public function sendContentType(string $contentType, int $code = 0): bool + public function sendHeaderContentType($contentType, int $code = 0): bool { return $this->sendHeader('Content-Type', $contentType, $code); } - public function sendRangeNotSatisfy() : never + public function getDetermineMimeType(): ?string + { + if ($this->isSendRealMimeType()) { + $mimeType = MimeType::fileMimeType($this->file->getRealPath()); + } else { + $mimeType = MimeType::mime($this->file->getExtension()); + } + return $mimeType; + } + + public function sendHeaderMimeType(): bool + { + $mimeType = $this->getDetermineMimeType(); + return $mimeType && $this->sendHeader('Content-Type', $mimeType); + } + + /** + * @return bool + */ + public function sendHeaderAttachment() : bool + { + if (!$this->isSendAsAttachment()) { + return false; + } + return $this->sendHeader( + 'Content-Disposition', + sprintf( + 'attachment; filename="%s"', + rawurlencode($this->getAttachmentFileName()) + ) + ); + } + + public function displayRangeNotSatisfy() : never { - $this->sendContentType('text/html', 416); + $this->sendHeaderContentType('text/html', 416); $this->sendHeader('Content-Range', 'bytes */'.$this->size); $this->stopRequest(); } - private function sendData(ServerRequestInterface $request) : never + public function send(?ServerRequestInterface $request = null): never + { + $request ??= ServerRequest::fromGlobals( + Decorator::service(ServerRequestFactoryInterface::class), + Decorator::service(StreamFactoryInterface::class), + ); + $method = strtoupper($request->getMethod()); + if (!in_array($method, self::ALLOWED_METHODS)) { + $exceptions = new MethodNotAllowedException( + $request + ); + $exceptions->setAllowedMethods(self::ALLOWED_METHODS); + throw $exceptions; + } + if (!$this->valid()) { + throw new SourceFileFailException( + $this->file->getPathname(), + sprintf( + 'File %s is not valid', + $this->file + ) + ); + } + // remove all buffer + $count = 5; + while (--$count > 0 && ob_get_level() > 0) { + ob_end_clean(); + } + if (headers_sent()) { + throw new HttpRuntimeException( + 'Header already sent' + ); + } + $this->sendRequestData($request); + } + + private function sendRequestData(ServerRequestInterface $request) : never { // remove x-powered-by php header_remove('X-Powered-By'); $method = strtoupper($request->getMethod()); if ($method === 'OPTIONS') { - $this->sendContentType('text/html'); + $this->sendHeaderContentType('text/html'); // just allow options get head post only - $this->sendAcceptRanges(); + $this->sendHeaderAcceptRanges(); // 604800 is 1 week - $this->sendCacheHeader(maxAge: 604800); + $this->sendHeaderCache(maxAge: 604800); $this->sendHeader('Allow', implode(', ', self::ALLOWED_METHODS)); exit(0); } - // get mime types - if ($this->isSendRealMimeType()) { - $mimeType = MimeType::fileMimeType($this->file->getRealPath()); - } else { - $mimeType = MimeType::mime($this->file->getExtension()); - } $fileSize = $this->size; $rangeHeader = trim($request->getHeaderLine('Range')); @@ -364,6 +415,8 @@ private function sendData(ServerRequestInterface $request) : never /** * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests */ + // get mime types + $mimeType = $this->getDetermineMimeType(); $rangeMimeType = $mimeType??self::DEFAULT_MIMETYPE; $totalRanges = 0; $maxRanges = $this->getMaxRanges(); @@ -384,7 +437,7 @@ private function sendData(ServerRequestInterface $request) : never if (($start === '' && $end === '')) { // stop - $this->sendRangeNotSatisfy(); + $this->displayRangeNotSatisfy(); } $start = $start === '' ? 0 : $start; @@ -399,7 +452,7 @@ private function sendData(ServerRequestInterface $request) : never $headers = null; $ranges = null; // stop - $this->sendRangeNotSatisfy(); + $this->displayRangeNotSatisfy(); } $start = (int) $start; @@ -457,13 +510,8 @@ private function sendData(ServerRequestInterface $request) : never // $this->sendAcceptRanges(); // if only 1 or empty ranges if (($empty = empty($ranges)) || $totalRanges === 1) { - if ($mimeType) { - $this->sendContentType($mimeType); - } - // send cache // $this->sendCacheHeader(['public', 'must-revalidate'], maxAge: 604800); - $startingPoint = 0; // if ranges if (!$empty) { @@ -477,14 +525,18 @@ private function sendData(ServerRequestInterface $request) : never } } // set content length - $this->sendContentLength($total); - - if ($this->isSendLastModifiedTime()) { - $this->sendLastModified(); - } + $this->sendHeaderContentLength($total); + // send mimetype header + $this->sendHeaderMimeType(); + // send etag + $this->sendHeaderEtag(); + // send last modifier + $this->sendHeaderLastModified(); + // send attachment header + $this->sendHeaderAttachment(); // set etag - $this->sendEtag(); + $this->sendHeaderEtag(); if ($method === 'HEAD') { $this->stopRequest(); } @@ -503,21 +555,23 @@ private function sendData(ServerRequestInterface $request) : never } if (!$this->isAllowRange()) { - $this->sendRangeNotSatisfy(); + $this->displayRangeNotSatisfy(); } // get socket $sock = $this->getSock(); // send boundary and status code -> partial content 206 - $this->sendContentType("multipart/byteranges; boundary=$boundary", 206); + $this->sendHeaderContentType("multipart/byteranges; boundary=$boundary", 206); // send range total - $this->sendContentLength($rangeTotal); + $this->sendHeaderContentLength($rangeTotal); // send etag - $this->sendEtag(); - if ($this->isSendLastModifiedTime()) { - $this->sendLastModified(); - } + $this->sendHeaderEtag(); + // send last modifier + $this->sendHeaderLastModified(); + // send attachment header + $this->sendHeaderAttachment(); + // no process if method header if ($method === 'HEAD') { $this->stopRequest(); diff --git a/src/Responder/Interfaces/FileResponderInterface.php b/src/Responder/Interfaces/FileResponderInterface.php index ecf0e73..34c72d7 100644 --- a/src/Responder/Interfaces/FileResponderInterface.php +++ b/src/Responder/Interfaces/FileResponderInterface.php @@ -15,9 +15,18 @@ public function getFile() : SplFileInfo; public function valid() : bool; public function setAllowRange(bool $enable); + public function isAllowRange() : bool; - public function setFileName(string $fileName); + public function setMaxRanges(int $ranges); + /** + * @return int + */ + public function getMaxRanges() : int; + + public function setAttachmentFileName(string $fileName); + + public function getAttachmentFileName(); public function resetFileName(); @@ -32,12 +41,10 @@ public function isSendAsAttachment(): bool; public function sendRealMimeType(bool $enable); public function isSendRealMimeType(): bool; - public function setMaxRanges(int $ranges); - /** - * @return int - */ - public function getMaxRanges() : int; + public function sendContentLength(bool $enable); + + public function isSendContentLength(): bool; public function getBoundary(): string; diff --git a/src/Routing/AbstractController.php b/src/Routing/AbstractController.php index 903e046..89d335d 100644 --- a/src/Routing/AbstractController.php +++ b/src/Routing/AbstractController.php @@ -3,8 +3,8 @@ namespace ArrayAccess\TrayDigita\Routing; -use ArrayAccess\TrayDigita\Container\Container; use ArrayAccess\TrayDigita\Container\ContainerWrapper; +use ArrayAccess\TrayDigita\Container\Interfaces\SystemContainerInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Http\Code; @@ -22,7 +22,6 @@ use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; use ArrayAccess\TrayDigita\Util\Filter\DataType; use JsonSerializable; -use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; @@ -54,7 +53,7 @@ abstract class AbstractController implements ControllerInterface private ?ResponseInterface $response = null; - protected ?ContainerInterface $container = null; + protected ?SystemContainerInterface $container = null; protected ?ManagerInterface $manager = null; @@ -64,8 +63,9 @@ abstract class AbstractController implements ControllerInterface final public function __construct(public readonly RouterInterface $router) { - $this->container = $this->router->getContainer(); - if ($this->container && $this instanceof ManagerAllocatorInterface) { + $container = $this->router->getContainer(); + $this->container = $container ? ContainerWrapper::maybeContainerOrCreate($container) : null; + if ($this instanceof ManagerAllocatorInterface) { $manager = ContainerHelper::use(ManagerInterface::class, $this->container); if ($manager) { $this->setManager($manager); @@ -161,10 +161,10 @@ public function renderJson( ); } - public function getContainer(): ContainerInterface|Container|null + public function getContainer(): SystemContainerInterface|null { if (!$this->container && ($container = $this->router->getContainer())) { - $this->container = $container; + $this->container = ContainerWrapper::maybeContainerOrCreate($container); } return $this->container; } diff --git a/src/Templates/Wrapper.php b/src/Templates/Wrapper.php index 4589521..6f2d952 100644 --- a/src/Templates/Wrapper.php +++ b/src/Templates/Wrapper.php @@ -15,19 +15,19 @@ final class Wrapper /** * @param ViewInterface $view - * @param string $publicDir + * @param string $publicDirectory * @param string $templatesPath */ public function __construct( public readonly ViewInterface $view, - string $publicDir, + string $publicDirectory, string $templatesPath = 'templates' ) { $this->templatePath = ltrim( DataNormalizer::normalizeDirectorySeparator($templatesPath, true), DIRECTORY_SEPARATOR ); - $this->publicDirectory = DataNormalizer::normalizeDirectorySeparator($publicDir, true); + $this->publicDirectory = DataNormalizer::normalizeDirectorySeparator($publicDirectory, true); } public function getView(): ViewInterface diff --git a/src/Traits/Service/CallStackTraceTrait.php b/src/Traits/Service/CallStackTraceTrait.php index 876abf3..cc8beab 100644 --- a/src/Traits/Service/CallStackTraceTrait.php +++ b/src/Traits/Service/CallStackTraceTrait.php @@ -13,7 +13,7 @@ trait CallStackTraceTrait { const MAX_CALLSTACK = 256; - private array $callStackIncrement = []; + protected array $callStackIncrement = []; private function getInternalCallStackName() : ?string { diff --git a/src/Uploader/Chunk.php b/src/Uploader/Chunk.php index 14b0aaa..0f1cda5 100644 --- a/src/Uploader/Chunk.php +++ b/src/Uploader/Chunk.php @@ -89,18 +89,18 @@ class Chunk implements ManagerAllocatorInterface, ContainerIndicateInterface public function __construct( protected ContainerInterface $container, - ?string $storageDir = null + ?string $storageDirectory = null ) { - if ($storageDir === null) { + if ($storageDirectory === null) { $config = ContainerHelper::service(Config::class); $path = $config?->get('path'); $storageDirectory = $path instanceof Config ? $path->get('storage') : null; - if (is_string($storageDirectory) - && is_dir($storageDirectory) + if (!is_string($storageDirectory) + || !is_dir($storageDirectory) ) { - $storageDir = $storageDirectory; + $storageDirectory = null; } } $manager = ContainerHelper::use( @@ -112,10 +112,10 @@ public function __construct( } $this->partialExtension = 'partial'; $this->partialMetaExtension = $this->partialExtension . '.meta'; - $storageDir = $storageDir??sys_get_temp_dir(); - $this->assertDirectory($storageDir); - $storageDir = (realpath($storageDir)??$storageDir); - $this->uploadCacheStorageDirectory = $storageDir + $storageDirectory = $storageDirectory??sys_get_temp_dir(); + $this->assertDirectory($storageDirectory); + $storageDirectory = (realpath($storageDirectory)??$storageDirectory); + $this->uploadCacheStorageDirectory = $storageDirectory . DIRECTORY_SEPARATOR . self::SUFFIX_STORAGE_DIRECTORY; $this->maxUploadFileSize = Consolidation::getMaxUploadSize(); diff --git a/src/Util/Filter/Consolidation.php b/src/Util/Filter/Consolidation.php index 4b59796..5f48486 100644 --- a/src/Util/Filter/Consolidation.php +++ b/src/Util/Filter/Consolidation.php @@ -425,7 +425,7 @@ public static function namespace(string|object $fullClassName) : string|false public static function isValidClassName(string $className) : bool { return (bool) preg_match( - '~^\\\?[A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]*(\\\[A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]*)*$~', + '~^\\\?[A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]*(?:\\\[A-Z-a-z_\x80-\xff]+[A-Z-a-z_0-9\x80-\xff]*)*$~', $className ); } diff --git a/src/Util/Parser/PhpClassParserSerial.php b/src/Util/Parser/PhpClassParserSerial.php index d8b846b..3974470 100644 --- a/src/Util/Parser/PhpClassParserSerial.php +++ b/src/Util/Parser/PhpClassParserSerial.php @@ -43,12 +43,13 @@ private function __construct(string $source) public static function fromSource(string $source): PhpClassParserSerial { $source = trim($source); - if ($source || !preg_match('~^\s*<\?php~i', $source)) { + if (!$source || !preg_match('~^<\?php~i', $source)) { unset($source); throw new UnsupportedArgumentException( 'The source maybe is not php class file', ); } + return new self($source); } diff --git a/src/View/Engines/TwigEngine.php b/src/View/Engines/TwigEngine.php index de640d4..05296f3 100644 --- a/src/View/Engines/TwigEngine.php +++ b/src/View/Engines/TwigEngine.php @@ -131,7 +131,7 @@ public function clearVariableCache(): void } $loader = $this->twig?->getLoader(); - $viewsDir = $this->getFilteredViewsDir(); + $viewsDirectory = $this->getFilteredViewsDir(); parent::clearVariableCache(); if ($loader instanceof FilesystemLoader) { $paths = $loader->getPaths(); @@ -144,7 +144,7 @@ public function clearVariableCache(): void if ($templateRule && str_starts_with($path, $templateRule)) { continue; } - if (!in_array($path, $viewsDir) && !in_array($path, $currentPaths)) { + if (!in_array($path, $viewsDirectory) && !in_array($path, $currentPaths)) { $currentPaths[] = $path; } } diff --git a/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php b/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php index de29e6d..31b2c86 100644 --- a/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php +++ b/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php @@ -3,6 +3,7 @@ namespace ArrayAccess\TrayDigita\View\ErrorRenderer; +use ArrayAccess\TrayDigita\Collection\Config; use ArrayAccess\TrayDigita\Exceptions\Runtime\MaximumCallstackExceeded; use ArrayAccess\TrayDigita\Http\Exceptions\HttpException; use ArrayAccess\TrayDigita\Kernel\Interfaces\KernelInterface; @@ -48,7 +49,12 @@ protected function format( Throwable $exception, bool $displayErrorDetails ): ?string { - if ($displayErrorDetails) { + $config = ContainerHelper::getNull( + Config::class, + $this->getContainer() + )?->get('environment'); + $isDebug = $config instanceof Config && $config->get('debug') === true; + if ($displayErrorDetails && $isDebug) { try { $kernel = ContainerHelper::use(KernelInterface::class, $this->getContainer()); $root = $kernel?->getRootDirectory()??PossibleRoot::getPossibleRootDirectory(); @@ -84,8 +90,8 @@ protected function format( return $view->render( $path, [ - 'displayErrorDetails' => false, - 'exception' => $exception + 'displayErrorDetails' => $displayErrorDetails, + 'exception' => $exception, ] ); } diff --git a/src/View/View.php b/src/View/View.php index 11bc7bd..d9b5184 100644 --- a/src/View/View.php +++ b/src/View/View.php @@ -86,21 +86,21 @@ class View implements ViewInterface, ManagerAllocatorInterface public function __construct( protected ContainerInterface $container, ?ManagerInterface $manager = null, - string|iterable $viewsDir = '', + string|iterable $viewsDirectory = '', ) { $manager ??= ContainerHelper::service(ManagerInterface::class, $this->container); $this->setManager($manager??Decorator::manager()); - if (empty($viewsDir)) { + if (empty($viewsDirectory)) { $config = ContainerHelper::use(Config::class, $this->container); $config = $config?->get('path')??null; $config = $config instanceof Config ? $config : null; $directory = $config?->get('view'); if ($directory) { - $viewsDir = $directory; + $viewsDirectory = $directory; } } - $this->setViewsDirectory($viewsDir); + $this->setViewsDirectory($viewsDirectory); } /** diff --git a/src/Web.php b/src/Web.php index 65afa30..09c51e8 100644 --- a/src/Web.php +++ b/src/Web.php @@ -53,7 +53,6 @@ final private function __construct() * @return ResponseInterface|false * @noinspection PhpMissingReturnTypeInspection * @noinspection PhpIssetCanBeReplacedWithCoalesceInspection - * @noinspection DuplicatedCode */ final public static function serve() { @@ -116,10 +115,10 @@ final public static function serve() ); } $publicFile = $publicFile ?: realpath(TD_INDEX_FILE); - $publicDir = dirname($publicFile); + $publicDirectory = dirname($publicFile); // HANDLE CLI-SERVER if (php_sapi_name() === 'cli-server') { - if ($_SERVER['DOCUMENT_ROOT'] !== $publicDir) { + if ($_SERVER['DOCUMENT_ROOT'] !== $publicDirectory) { throw new RuntimeException( "Builtin web server should be pointing into public root directory!" ); @@ -141,7 +140,7 @@ final public static function serve() |mp[34]|og[gvpa]|mpe?g|3gp|avi|mov|flac|flv|webm|wmv # media )$~ix', $requestUriNoQuery - ) && is_file($publicDir . '/' . $requestUriNoQuery)) { + ) && is_file($publicDirectory . '/' . $requestUriNoQuery)) { // serve the static assets with return : false return $lastResult = false; } @@ -152,7 +151,7 @@ final public static function serve() if (!defined('TD_APP_DIRECTORY')) { // DEFINE : TD_APP_DIRECTORY - define('TD_APP_DIRECTORY', dirname($publicDir) . DIRECTORY_SEPARATOR . 'app'); + define('TD_APP_DIRECTORY', dirname($publicDirectory) . DIRECTORY_SEPARATOR . 'app'); } /** @@ -235,7 +234,10 @@ class_alias('Exception', 'Throwable'); ) { $config = ContainerHelper::use(Config::class, $kernel->getHttpKernel()->getContainer()); $config = $config->get('environment'); - $enable = $config instanceof Config && $config->get('displayErrorDetails') === true; + $enable = $config instanceof Config && ( + $config->get('displayErrorDetails') === true + || $config->get('debug') === true + ); } $additionalText = ! $enable ? "

$message

" : <<$message