diff --git a/app/Common/Option.php b/app/Common/Option.php index d14ce6a..77df699 100644 --- a/app/Common/Option.php +++ b/app/Common/Option.php @@ -2,6 +2,7 @@ namespace App\Common; +use App\Core\Helper; use App\Exceptions\ClassNotFoundException; use ReflectionClass; @@ -38,62 +39,7 @@ public function set($name, $value) $this->$name = $value; } - /** - * @param \ReflectionProperty[] $objectProperties - * @return array - */ - protected function extractPropertyNames($objectProperties): array - { - $properties = []; - foreach ($objectProperties as $objectProperty) { - array_push($properties, $objectProperty->getName()); - } - return $properties; - } - - protected function filterInvalidProperties($valueKeys, $propertyNames): array - { - $invalueProperties = []; - foreach ($valueKeys as $valueKey) { - if (!in_array($valueKey, $propertyNames)) { - array_push($invalueProperties, $valueKey); - } - } - return $invalueProperties; - } - protected function mapOptionValueToObject($rawValue, $className = null) - { - if (is_null($className) || !is_array($rawValue)) { - return $rawValue; - } - if (!class_exists($className, true)) { - throw new ClassNotFoundException($className); - } - $reflectClass = new ReflectionClass($className); - $object = $reflectClass->newInstance(); - $properties = $this->extractPropertyNames($reflectClass->getProperties()); - $validProperies = $this->filterInvalidProperties(array_keys($rawValue), $properties); - - foreach ($rawValue as $key => $value) { - if (!in_array($key, $validProperies)) { - $property = $reflectClass->getProperty($key); - $property->setAccessible(true); - $propertyType = $property->getType(); - if (is_array($value) && !empty($propertyType) && class_exists($propertyType->getName())) { - $property->setValue( - $object, - $this->mapOptionValueToObject($value, $propertyType->getName()) - ); - } else { - $property->setValue($object, $value); - } - $property->setAccessible(false); - } - } - - return $object; - } public function get($name, $defaultValue = null, $mapToObjectClass = null) { @@ -102,7 +48,7 @@ public function get($name, $defaultValue = null, $mapToObjectClass = null) if (is_null($mapToObjectClass)) { return $value; } - return $this->mapOptionValueToObject($value, $mapToObjectClass); + return Helper::convertArrayValuesToObject($value, $mapToObjectClass); } public static function __callStatic($name, $arguments) diff --git a/app/Constracts/AssetConstract.php b/app/Constracts/AssetConstract.php new file mode 100644 index 0000000..13e9c49 --- /dev/null +++ b/app/Constracts/AssetConstract.php @@ -0,0 +1,28 @@ +type = $type; + } + + public static function init() + { + if (static::$inited === false) { + self::$CSS = new self('css'); + self::$JS = new self('js'); + self::$ICON = new self('icon'); + self::$FONT = new self('font'); + self::$STYLE = new self('style'); + self::$SCRIPT = new self('script'); + + // Make inited flag is true + static::$inited = true; + } + } + + public static function CSS(): AssetTypeEnum + { + return static::$CSS; + } + + public static function JS(): AssetTypeEnum + { + return static::$JS; + } + + public static function ICON(): AssetTypeEnum + { + return static::$ICON; + } + + public static function FONT(): AssetTypeEnum + { + return static::$FONT; + } + + public static function STYLE(): AssetTypeEnum + { + return static::$STYLE; + } + + public static function SCRIPT(): AssetTypeEnum + { + return static::$SCRIPT; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + public function __toString() + { + return $this->getType(); + } +} diff --git a/app/Constracts/ExternalAssetConstract.php b/app/Constracts/ExternalAssetConstract.php new file mode 100644 index 0000000..4ae77e9 --- /dev/null +++ b/app/Constracts/ExternalAssetConstract.php @@ -0,0 +1,12 @@ +id = $id; + } + + public function isValid(): bool + { + return !empty($this->id); + } + + public function setAssetType(AssetTypeEnum $assetType): AssetConstract + { + $this->assetType = $assetType; + + return $this; + } + + public function getAssetType(): AssetTypeEnum + { + return $this->assetType; + } + + public function setDeps($deps): AssetConstract + { + if (is_array($deps)) { + $this->deps = $deps; + } + return $this; + } + + public function setOptions(AssetOptions $assetOptions): AssetConstract + { + $this->options = $assetOptions; + + return $this; + } + + public function setPriority(int $priority): AssetConstract + { + $this->priority = $priority; + + return $this; + } + + public function setVersion($version): AssetConstract + { + $this->version = $version; + + return $this; + } + + public function getId() + { + return $this->id; + } +} diff --git a/app/Core/AssetManager.php b/app/Core/AssetManager.php new file mode 100644 index 0000000..007f906 --- /dev/null +++ b/app/Core/AssetManager.php @@ -0,0 +1,122 @@ +frontendBucket = new Bucket(); + $this->backendBucket = new Bucket(); + } + + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new self(); + } + return static::$instance; + } + + public static function create( + $id, + AssetUrl $url, + AssetTypeEnum $assetType, + $deps = [], + $version = null, + AssetOptions $assetOptions = null, + $priority = 10 + ): AssetConstract { + $asset = Helper::createAssetByAssetType($id, $assetType); + if ($asset instanceof ExternalAssetConstract) { + $asset->setUrl($url); + } + $asset->setDeps($deps); + $asset->setOptions($assetOptions); + $asset->setPriority($priority); + $asset->setVersion($version); + + return $asset; + } + + public static function registerAsset( + $id, + AssetUrl $url, + AssetTypeEnum $assetType, + $deps = [], + $version = null, + AssetOptions $assetOptions = null, + $priority = 10 + ): self { + $instance = static::getInstance(); + $instance->getFrontendBucket() + ->addAsset( + static::create($id, $url, $assetType, $deps, $version, $assetOptions, $priority) + ); + return $instance; + } + + public static function registerBackendAsset( + $id, + AssetUrl $url, + AssetTypeEnum $assetType, + $deps = [], + $version = null, + AssetOptions $assetOptions = null, + $priority = 10 + ): self { + $instance = static::getInstance(); + $instance->getBackendBucket() + ->addAsset( + static::create($id, $url, $assetType, $deps, $version, $assetOptions, $priority) + ); + return $instance; + } + + public function getFrontendBucket(): Bucket + { + return $this->frontendBucket; + } + + public function getBackendBucket(): Bucket + { + return $this->backendBucket; + } + + public function printInitHeadScripts() + { + } + + public function printHeadAssets() + { + } + + public function printExecuteHeadScripts() + { + } + + public function printFooterInitScripts() + { + } + + public function printFooterAssets() + { + } + + public function executeFooterScripts() + { + } +} diff --git a/app/Core/Assets/AssetOptions.php b/app/Core/Assets/AssetOptions.php new file mode 100644 index 0000000..358e524 --- /dev/null +++ b/app/Core/Assets/AssetOptions.php @@ -0,0 +1,16 @@ +url = $url; + if (!is_null($minUrl)) { + $this->minUrl = $minUrl; + } + } + + public function getUrl($supportMinUrl = false) + { + if (!$supportMinUrl || empty($this->minUrl)) { + return $this->url; + } + return $this->minUrl; + } + + public function __toString() + { + return $this->getUrl(); + } +} diff --git a/app/Core/Assets/Bucket.php b/app/Core/Assets/Bucket.php new file mode 100644 index 0000000..29b8f3d --- /dev/null +++ b/app/Core/Assets/Bucket.php @@ -0,0 +1,24 @@ +isValid()) { + return; + } + $this->assets[$asset->getAssetType()->getType()][$asset->getId()] = $asset; + } +} diff --git a/app/Core/Assets/CascadingStyleSheets.php b/app/Core/Assets/CascadingStyleSheets.php new file mode 100644 index 0000000..31c0899 --- /dev/null +++ b/app/Core/Assets/CascadingStyleSheets.php @@ -0,0 +1,12 @@ +registerModule = function (string $moduleName, ExtensionConstract $extension): self { - $this->extensions[$moduleName] = $extension; - - return $this; - }; - - $this->getModule = function ($moduleName): ExtensionConstract { - if (isset($this->extensions[$moduleName])) { - return $this->extensions[$moduleName]; - } - throw new \Exception("Module {$moduleName} is not registered."); - }; - - - $this->hasExtension = function ($extension): bool { - return isset($this->extensions[$extension]); - }; } public static function getInstance() @@ -62,9 +40,9 @@ public static function getInstance() public static function __callStatic($name, $arguments) { $instance = static::getInstance(); - $callable = $instance->$name; + $callable = [$instance, $name]; - if (!property_exists($instance, $name) || !is_callable($callable)) { + if (!is_callable($callable)) { throw new RuntimeException(sprintf("The method %s::%s() is not defined", __CLASS__, $name)); } return call_user_func_array($callable, $arguments); @@ -126,10 +104,10 @@ protected static function parseExtensionInfo($json): ExtensionInfo public function addActiveExtension(ExtensionConstract $extension) { - array_push($this->activeExtensions, $extension); + $this->activeExtensions[$extension->getExtensionName()] = $extension; } - public function loadExtensions(&$app, &$container) + public function init(&$app, &$container) { $instance = static::getInstance(); $extensionResolver = new ExtensionResolver($app, $container); @@ -171,4 +149,19 @@ public function runActiveExtensions() $extension->run(); } } + + protected function getExtension($extensionName, $throwException = true) + { + if (isset($this->activeExtensions[$extensionName])) { + return $this->activeExtensions[$extensionName]; + } + if ($throwException) { + throw new NotFoundExtensionException($extensionName); + } + } + + protected function hasExtension($extensionName) + { + return isset($this->activeExtensions[$extensionName]); + } } diff --git a/app/Core/ExternalAsset.php b/app/Core/ExternalAsset.php new file mode 100644 index 0000000..9970ddb --- /dev/null +++ b/app/Core/ExternalAsset.php @@ -0,0 +1,27 @@ +url = $url; + + return $this; + } + + public function getUrl($supportMinUrl = false) + { + if (empty($this->url)) { + return ""; + } + + return $this->url->getUrl($supportMinUrl); + } +} diff --git a/app/Core/Helper.php b/app/Core/Helper.php new file mode 100644 index 0000000..6c1e799 --- /dev/null +++ b/app/Core/Helper.php @@ -0,0 +1,112 @@ +getType()) { + case AssetTypeEnum::CSS(): + return (new CascadingStyleSheets($id)) + ->setAssetType($assetType); + case AssetTypeEnum::FONT(): + return (new Font($id)) + ->setAssetType($assetType); + case AssetTypeEnum::ICON(): + return (new Icon($id)) + ->setAssetType($assetType); + case AssetTypeEnum::JS(): + return (new JavaScript($id)) + ->setAssetType($assetType); + case AssetTypeEnum::SCRIPT(): + return (new Script($id)) + ->setAssetType($assetType); + case AssetTypeEnum::STYLE(): + return (new Style($id)) + ->setAssetType($assetType); + } + throw new InvalidAssetTypeException($assetType); + } + + + /** + * @param \ReflectionProperty[] $objectProperties + * @return array + */ + protected static function extractPropertyNames($objectProperties): array + { + $properties = []; + foreach ($objectProperties as $objectProperty) { + array_push($properties, $objectProperty->getName()); + } + return $properties; + } + + protected static function filterInvalidProperties($valueKeys, $propertyNames): array + { + $invalueProperties = []; + foreach ($valueKeys as $valueKey) { + if (!in_array($valueKey, $propertyNames)) { + array_push($invalueProperties, $valueKey); + } + } + return $invalueProperties; + } + + public static function convertArrayValuesToObject($rawValue, $className = null) + { + if (is_null($className) || !is_array($rawValue)) { + return $rawValue; + } + if (!class_exists($className, true)) { + throw new ClassNotFoundException($className); + } + $reflectClass = new ReflectionClass($className); + $object = $reflectClass->newInstance(); + $properties = static::extractPropertyNames($reflectClass->getProperties()); + $validProperies = static::filterInvalidProperties(array_keys($rawValue), $properties); + + foreach ($rawValue as $key => $value) { + if (!in_array($key, $validProperies)) { + $property = $reflectClass->getProperty($key); + $property->setAccessible(true); + $propertyType = $property->getType(); + if (is_array($value) && !empty($propertyType) && class_exists($propertyType->getName())) { + $property->setValue( + $object, + static::convertArrayValuesToObject($value, $propertyType->getName()) + ); + } else { + $property->setValue($object, $value); + } + $property->setAccessible(false); + } + } + + return $object; + } +} diff --git a/app/Core/MiddlewareDispatcher.php b/app/Core/MiddlewareDispatcher.php new file mode 100644 index 0000000..cb0cda1 --- /dev/null +++ b/app/Core/MiddlewareDispatcher.php @@ -0,0 +1,29 @@ +seedMiddlewareStack($kernel); + $this->callableResolver = $callableResolver; + $this->container = $container; + } +} diff --git a/app/Core/Routing/Route.php b/app/Core/Routing/Route.php index 496f10c..8377cd0 100644 --- a/app/Core/Routing/Route.php +++ b/app/Core/Routing/Route.php @@ -3,6 +3,7 @@ namespace App\Core\Routing; use App\Core\Handlers\Strategies\RequestResponse; +use App\Core\MiddlewareDispatcher; use Psr\Container\ContainerInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseFactoryInterface; @@ -13,7 +14,6 @@ use Slim\Interfaces\AdvancedCallableResolverInterface; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\InvocationStrategyInterface; -use Slim\MiddlewareDispatcher; use Slim\Routing\Route as SlimRoute; class Route extends SlimRoute diff --git a/app/Exceptions/InvalidAssetTypeException.php b/app/Exceptions/InvalidAssetTypeException.php new file mode 100644 index 0000000..d83a05d --- /dev/null +++ b/app/Exceptions/InvalidAssetTypeException.php @@ -0,0 +1,20 @@ +getType() + '` is invalid'; + } + parent::__construct($message, $code); + } +} diff --git a/app/Exceptions/NotFoundExtensionException.php b/app/Exceptions/NotFoundExtensionException.php new file mode 100644 index 0000000..71376be --- /dev/null +++ b/app/Exceptions/NotFoundExtensionException.php @@ -0,0 +1,13 @@ +load(); } + + // Init extension manager + $this->extensionManager = ExtensionManager::getInstance(); + + // Init asset type enum + AssetTypeEnum::init(); } protected function setup() @@ -128,7 +152,7 @@ protected function setup() $this->container->set('option', Option::getInstance()); // Load extension system - ExtensionManager::getInstance()->loadExtensions($this->app, $this->container); + $this->extensionManager->init($this->app, $this->container); } public function boot() @@ -147,7 +171,23 @@ protected function loadExtensions() HookManager::executeAction('loaded_extensions'); // Run all active extensions - ExtensionManager::getInstance()->runActiveExtensions(); + $this->extensionManager->runActiveExtensions(); + } + + protected function setupAssets() + { + // Setup assets after extensions are loaded + $assetManager = AssetManager::getInstance(); + + // Setup assets in tag + HookManager::addAction('head', [$assetManager, 'printInitHeadScripts'], 33); + HookManager::addAction('head', [$assetManager, 'printHeadAssets'], 66); + HookManager::addAction('head', [$assetManager, 'printExecuteHeadScripts'], 99); + + // Setup asset before tag + HookManager::addAction('footer', [$assetManager, 'printFooterInitScripts'], 33); + HookManager::addAction('footer', [$assetManager, 'printFooterAssets'], 66); + HookManager::addAction('footer', [$assetManager, 'executeFooterScripts'], 99); } protected function run() @@ -156,6 +196,8 @@ protected function run() $this->setupHttpErrorHandle() ); + $this->setupAssets(); + // Run App & Emit Response $response = $this->app->handle($this->request); diff --git a/app/helpers.php b/app/helpers.php index 276f1e4..4d5a9bb 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -2,7 +2,9 @@ use App\Common\Constants; use App\Common\Option; +use App\Core\Application; use App\Core\HookManager; +use Puleeno\Bootstrap; if (!function_exists('array_get')) { function array_get($arr, $keyStr, $defaultValue = null) @@ -49,3 +51,10 @@ function add_filter($hookName, $fn, $priority = 10, $paramsQuantity = 1) return HookManager::addFilter($hookName, $fn, $priority, $paramsQuantity); } } + +if (!function_exists('get_app')) { + function get_app(): Application + { + return Bootstrap::getInstance()->getApp(); + } +} diff --git a/extensions/dashboard b/extensions/dashboard index 07fb813..7c01e4f 160000 --- a/extensions/dashboard +++ b/extensions/dashboard @@ -1 +1 @@ -Subproject commit 07fb81300135e139e488179bd04e9780ff897a02 +Subproject commit 7c01e4f9a8f16658402ba8c4570d0cdb1e510ee0 diff --git a/extensions/layout b/extensions/layout index a59d2e0..3f3b5d7 160000 --- a/extensions/layout +++ b/extensions/layout @@ -1 +1 @@ -Subproject commit a59d2e0bb7c0edd4588ff97662e1bba231c38a02 +Subproject commit 3f3b5d7f064021e381393dfb8605185277aba087 diff --git a/public/index.php b/public/index.php index cc5d3f0..10bffce 100644 --- a/public/index.php +++ b/public/index.php @@ -6,5 +6,5 @@ use Puleeno\Bootstrap; require __DIR__ . '/../app/bootstrap.php'; -$bootstrap = new Bootstrap(); +$bootstrap = Bootstrap::getInstance(); $bootstrap->boot();