commit 22296b8d7c8b528089189a8d9500395424cb6468 Author: Madeorsk Date: Fri Nov 8 12:12:38 2024 +0100 Initialize Nest core library. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dfd9ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# IDEA +.idea/ +*.iml + +# Composer +vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4eb0682 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "version": "1.0", + "name": "nest/core", + "description": "Nest framework core.", + "type": "library", + "authors": [ + { + "name": "Madeorsk", + "email": "madeorsk@protonmail.com" + } + ], + "autoload": { + "psr-4": { + "Nest\\": "src/" + }, + "files": [ + "src/Utils/Array.php", + "src/Utils/Paths.php", + "src/Utils/Reflection.php", + "src/Utils/String.php" + ] + }, + "require": { + "php": "^8.3" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..fc97def --- /dev/null +++ b/composer.lock @@ -0,0 +1,20 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f86ab8aef4bacd58e94fb6ceae48e892", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.3" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/src/Application.php b/src/Application.php new file mode 100644 index 0000000..db27ea6 --- /dev/null +++ b/src/Application.php @@ -0,0 +1,209 @@ + + */ + private array $servicesConfigurations; + + + /** + * Initialize a new application. + * @param string $basePath Base path of the application. + * @param string|null $globalContextIdentifier Global context identifier. This is useful if you need different configurations and initializations depending on call context. + * @throws UndefinedServiceConfigurationException + */ + public function __construct(string $basePath, private readonly ?string $globalContextIdentifier = null) + { + $this->paths = $this->initializePaths($basePath); + $this->configure(); + $this->initializeServices(); + $this->initialize(); + } + + /** + * Get the global context identifier. This is useful if you need different configurations and initializations depending on call context. + * @return string|null The global context identifier. + */ + public function getGlobalContextIdentifier(): ?string + { + return $this->globalContextIdentifier; + } + + + /** + * Initialize application paths. + * @param string $basePath Base path of the application. + * @return Paths Paths builder. + */ + protected function initializePaths(string $basePath): Paths + { + // Create default paths builder. + return new Paths($basePath); + } + + /** + * Application paths. + * @return Paths + */ + public function paths(): Paths + { + return $this->paths; + } + + + /** + * Initialize application services. + * @return void + * @throws UndefinedServiceConfigurationException + */ + private function initializeServices(): void + { + foreach (class_uses($this) as $usedTrait) + { + // Build the service initializer name. + $serviceInitializationMethod = "__nest__".get_class_name_from_fullname($usedTrait); + if (method_exists($this, $serviceInitializationMethod)) + // If there is a service initializer, we run it. + $this->$serviceInitializationMethod(); + } + } + + /** + * Set a service configuration for a given base class. + * @param string $serviceConfigurationBaseClass The service configuration base class. + * @param ServiceConfiguration $serviceConfiguration Initialized service configuration instance. + * @return void + * @throws InvalidServiceConfigurationDefinitionException + */ + protected function setServiceConfiguration(string $serviceConfigurationBaseClass, ServiceConfiguration $serviceConfiguration): void + { + if (!is_a($serviceConfiguration, $serviceConfigurationBaseClass)) + throw new InvalidServiceConfigurationDefinitionException($serviceConfigurationBaseClass, get_class($serviceConfiguration)); + + $this->servicesConfigurations[$serviceConfigurationBaseClass] = $serviceConfiguration; + } + + /** + * Get a service configuration by its base class. + * @template T of ServiceConfiguration + * @param class-string $serviceConfigurationClass Base class of the service configuration. + * @return T The service configuration instance. + * @throws UndefinedServiceConfigurationException + */ + protected function getServiceConfiguration(string $serviceConfigurationClass): ServiceConfiguration + { + if (empty($this->servicesConfigurations[$serviceConfigurationClass])) + throw new UndefinedServiceConfigurationException($serviceConfigurationClass); + + /** + * Get service configuration. + * @var T $config + */ + $config = $this->servicesConfigurations[$serviceConfigurationClass]; + return $config; + } + + /** + * Setup application configuration. + * Started before services initialization and application initialization. + * @return void + */ + protected abstract function configure(): void; + + + /** + * Initialize application. + * Started after services initialization. + * @return void + */ + public function initialize(): void + {} + + /** + * Exception handler instance. + * @var ExceptionHandler + */ + private ExceptionHandler $exceptionHandler; + + /** + * Get a new exception handler instance. + * @return ExceptionHandler New exception handler instance. + */ + protected function newExceptionHandler(): ExceptionHandler + { + // Return default exception handler. + return new ExceptionHandler(); + } + + /** + * Get exception handler instance for this application. + * @return ExceptionHandler Exception handler instance. + */ + public function exceptionHandler(): ExceptionHandler + { + if (empty($this->exceptionHandler)) + // No exception handler, initializing one. + $this->exceptionHandler = $this->newExceptionHandler(); + // Return initialized exception handler. + return $this->exceptionHandler; + } + + /** + * Start an engine in the application. + * @param callable $engine Engine instance or callable. + * @return void + */ + public function startEngine(callable $engine): void + { + // Set application exception handler while executing the engine. + set_exception_handler($this->exceptionHandler()); + $engine(); + restore_exception_handler(); + } +} diff --git a/src/ExceptionHandler.php b/src/ExceptionHandler.php new file mode 100644 index 0000000..b6a26ec --- /dev/null +++ b/src/ExceptionHandler.php @@ -0,0 +1,41 @@ +handlers as $handler) + { // Execute each registered exception handler. + $handler($exception); + } + } + + /** + * Add an exception handler. + * @param callable(Throwable): void $handler The exception handler function. + * @return $this + */ + public function add(callable $handler): static + { + $this->handlers[] = $handler; + return $this; + } +} diff --git a/src/Exceptions/Exception.php b/src/Exceptions/Exception.php new file mode 100644 index 0000000..baf1001 --- /dev/null +++ b/src/Exceptions/Exception.php @@ -0,0 +1,22 @@ +expectedType, got $this->actualType.", $code, $previous); + } +} diff --git a/src/Exceptions/Services/Configuration/InvalidServiceConfigurationDefinitionException.php b/src/Exceptions/Services/Configuration/InvalidServiceConfigurationDefinitionException.php new file mode 100644 index 0000000..dbaef52 --- /dev/null +++ b/src/Exceptions/Services/Configuration/InvalidServiceConfigurationDefinitionException.php @@ -0,0 +1,23 @@ +serviceConfigurationBaseClass}, {$this->givenServiceConfigurationClass} given.", $code, $previous); + } +} diff --git a/src/Exceptions/Services/Configuration/UndefinedServiceConfigurationException.php b/src/Exceptions/Services/Configuration/UndefinedServiceConfigurationException.php new file mode 100644 index 0000000..70d0e67 --- /dev/null +++ b/src/Exceptions/Services/Configuration/UndefinedServiceConfigurationException.php @@ -0,0 +1,22 @@ +serviceConfigurationClass}", $code, $previous); + } +} diff --git a/src/Exceptions/Services/ServiceException.php b/src/Exceptions/Services/ServiceException.php new file mode 100644 index 0000000..722f819 --- /dev/null +++ b/src/Exceptions/Services/ServiceException.php @@ -0,0 +1,9 @@ +methodName in $this->className.", $code, $previous); + } +} diff --git a/src/Paths.php b/src/Paths.php new file mode 100644 index 0000000..948012f --- /dev/null +++ b/src/Paths.php @@ -0,0 +1,43 @@ +appPath; + } + + /** + * Get temporary files path. + * @return string + */ + public function getTempPath(): string + { + return "{$this->getAppPath()}/tmp"; + } + + /** + * Get configuration files path. + * @return string + */ + public function getConfigPath(): string + { + return "{$this->getAppPath()}/config"; + } +} diff --git a/src/Services/ServiceConfiguration.php b/src/Services/ServiceConfiguration.php new file mode 100644 index 0000000..03cc993 --- /dev/null +++ b/src/Services/ServiceConfiguration.php @@ -0,0 +1,7 @@ + $value) + { // For each (key => value) association, tests if the test function returns true. + if ($test_function($value, $key)) + // If it returns true, we already can return true as we only search for one element that pass. + return true; + } + + return false; // We found no (key => value) association that passed the test, returning false. +} + diff --git a/src/Utils/Paths.php b/src/Utils/Paths.php new file mode 100644 index 0000000..2aea9e5 --- /dev/null +++ b/src/Utils/Paths.php @@ -0,0 +1,13 @@ + $expected_type Expected class type. + * @return true + * @throws InvalidTypeException + */ +function expect_type(mixed $object_or_class, string $expected_type): true +{ + if (!is_a($object_or_class, $expected_type)) + throw new InvalidTypeException($expected_type, $object_or_class); + return true; +} + +/** + * Get all classes of an object or a class. + * @param string|object $object_or_class + * @return string[] All classes of an object or a class (full inheritance tree). + */ +function get_all_classes(mixed $object_or_class): array +{ + // Get the class of the current object. + if (!is_string($object_or_class)) + $object_or_class = get_class($object_or_class); + + // Try to get the parent class of the current class. + $parentClass = get_parent_class($object_or_class); + + // Merge classes list, preprending with the current class. + return [$object_or_class, ...($parentClass ? get_all_classes($parentClass) : [])]; +} diff --git a/src/Utils/String.php b/src/Utils/String.php new file mode 100644 index 0000000..ba30fd6 --- /dev/null +++ b/src/Utils/String.php @@ -0,0 +1,82 @@ + "{$part}s", $parts); + // Glue all words together with '_'. + return implode("_", $parts); +} + +/** + * Get the singular form of every word in the snake_cased string. + * @param string $snake_name_to_singularize The snake_cased string to singularize. + * @return string The singularized snake_cased string. + */ +function str_snake_singularize(string $snake_name_to_singularize): string +{ + // Break the string to an array containing each word. + $parts = explode("_", $snake_name_to_singularize); + // Remove an 's' from each word, if there is one. + $parts = array_map(function (string $part) { + $sPos = strlen($part) - 1; + return $part[$sPos] == 's' ? substr($part, 0, $sPos) : $part; + }, $parts); + // Glue all words together with '_'. + return implode("_", $parts); +} + +/** + * Remove prefixes from a base string, if present. + * @param string $base Base string. + * @param string[] $prefix Prefix to remove, if present. + * @return string + */ +function str_remove_prefix(string $base, string ...$prefix): string +{ + foreach($prefix as $_prefix) + { // Remove each passed prefix. + if (str_starts_with($base, $_prefix)) + // Prefix is present, removing it. + $base = substr($base, strlen($_prefix)); + } + + return $base; // Return altered base. +} + +/** + * Remove suffixes from a base string, if present. + * @param string $base Base string. + * @param string[] $suffix Suffix to remove, if present. + * @return string + */ +function str_remove_suffix(string $base, string ...$suffix): string +{ + foreach($suffix as $_suffix) + { // Remove each passed suffix. + if (str_ends_with($base, $_suffix)) + // Suffix is present, removing it. + $base = substr($base, 0, strlen($base) - strlen($_suffix)); + } + + return $base; // Return altered base. +}