Initialize Nest core library.
This commit is contained in:
commit
22296b8d7c
17 changed files with 693 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Composer
|
||||
vendor/
|
26
composer.json
Normal file
26
composer.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
20
composer.lock
generated
Normal file
20
composer.lock
generated
Normal file
|
@ -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"
|
||||
}
|
209
src/Application.php
Normal file
209
src/Application.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
namespace Nest;
|
||||
|
||||
use Nest\Exceptions\Services\Configuration\InvalidServiceConfigurationDefinitionException;
|
||||
use Nest\Exceptions\Services\Configuration\UndefinedServiceConfigurationException;
|
||||
use Nest\Services\ServiceConfiguration;
|
||||
use function Nest\Utils\get_class_name_from_fullname;
|
||||
|
||||
/**
|
||||
* Main nest application class.
|
||||
*/
|
||||
abstract class Application
|
||||
{
|
||||
/**
|
||||
* Main instance of the application.
|
||||
* @var static
|
||||
*/
|
||||
private static Application $application;
|
||||
|
||||
/**
|
||||
* Register the created application.
|
||||
* @param static $application The application.
|
||||
* @return static The application.
|
||||
*/
|
||||
public static function register(Application $application): static
|
||||
{
|
||||
self::$application = $application;
|
||||
return $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main instance of the application.
|
||||
* @return static The application.
|
||||
*/
|
||||
public static function get(): static
|
||||
{
|
||||
return self::$application;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paths builder.
|
||||
* @var Paths
|
||||
*/
|
||||
private Paths $paths;
|
||||
|
||||
/**
|
||||
* Services configurations associative array.
|
||||
* @var array<string, ServiceConfiguration>
|
||||
*/
|
||||
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<T> $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();
|
||||
}
|
||||
}
|
41
src/ExceptionHandler.php
Normal file
41
src/ExceptionHandler.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Nest;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Default exception handler for Nest application.
|
||||
* Support multiple sub-handlers, typically added by engines.
|
||||
*/
|
||||
class ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* @var (callable(Throwable): void)[]
|
||||
*/
|
||||
protected array $handlers = [];
|
||||
|
||||
/**
|
||||
* Run all exception handlers.
|
||||
* @param Throwable $exception The exception.
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke(Throwable $exception): void
|
||||
{
|
||||
foreach ($this->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;
|
||||
}
|
||||
}
|
22
src/Exceptions/Exception.php
Normal file
22
src/Exceptions/Exception.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions;
|
||||
|
||||
use function Nest\Utils\get_class_name_from_fullname;
|
||||
use function Nest\Utils\str_remove_suffix;
|
||||
|
||||
/**
|
||||
* Generic class of a Nest exception.
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
* Get generic error ID from class name.
|
||||
* @return string Error ID.
|
||||
*/
|
||||
public function getErrorId(): string
|
||||
{
|
||||
// Get the current exception class name and remove "Exception" suffix.
|
||||
return str_remove_suffix(get_class_name_from_fullname(get_class($this)), "Exception");
|
||||
}
|
||||
}
|
22
src/Exceptions/InvalidTypeException.php
Normal file
22
src/Exceptions/InvalidTypeException.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Thrown when an invalid class or object is passed to a function.
|
||||
*/
|
||||
class InvalidTypeException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $expectedType The expected type.
|
||||
* @param string $actualType The actual class or object type.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $expectedType, public string $actualType, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Expected type $this->expectedType, got $this->actualType.", $code, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions\Services\Configuration;
|
||||
|
||||
use Nest\Exceptions\Services\ServiceException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when an invalid service configuration is defined for a service.
|
||||
*/
|
||||
class InvalidServiceConfigurationDefinitionException extends ServiceException
|
||||
{
|
||||
/**
|
||||
* @param string $serviceConfigurationBaseClass Service configuration base class.
|
||||
* @param string $givenServiceConfigurationClass Given service configuration class.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $serviceConfigurationBaseClass, public string $givenServiceConfigurationClass, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Invalid service configuration, expected {$this->serviceConfigurationBaseClass}, {$this->givenServiceConfigurationClass} given.", $code, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions\Services\Configuration;
|
||||
|
||||
use Nest\Exceptions\Services\ServiceException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a requested service configuration is not defined.
|
||||
*/
|
||||
class UndefinedServiceConfigurationException extends ServiceException
|
||||
{
|
||||
/**
|
||||
* @param string $serviceConfigurationClass Requested service configuration.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $serviceConfigurationClass, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Undefined service configuration: {$this->serviceConfigurationClass}", $code, $previous);
|
||||
}
|
||||
}
|
9
src/Exceptions/Services/ServiceException.php
Normal file
9
src/Exceptions/Services/ServiceException.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions\Services;
|
||||
|
||||
use Nest\Exceptions\Exception;
|
||||
|
||||
class ServiceException extends Exception
|
||||
{
|
||||
}
|
22
src/Exceptions/UnknownInstanceMethodException.php
Normal file
22
src/Exceptions/UnknownInstanceMethodException.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exception thrown when an instance method does not exists.
|
||||
*/
|
||||
class UnknownInstanceMethodException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $className Class where method is not found.
|
||||
* @param string $methodName Method not found.
|
||||
* @param int $code [optional] The Exception code.
|
||||
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||
*/
|
||||
public function __construct(public string $className, public string $methodName, int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Cannot find $this->methodName in $this->className.", $code, $previous);
|
||||
}
|
||||
}
|
43
src/Paths.php
Normal file
43
src/Paths.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Nest;
|
||||
|
||||
/**
|
||||
* Application paths builder.
|
||||
*/
|
||||
class Paths
|
||||
{
|
||||
/**
|
||||
* Create a new paths manager from its main path.
|
||||
* @param string $appPath The main path of the application.
|
||||
*/
|
||||
public function __construct(protected string $appPath)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Get main path of the application.
|
||||
* @return string
|
||||
*/
|
||||
public function getAppPath(): string
|
||||
{
|
||||
return $this->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";
|
||||
}
|
||||
}
|
7
src/Services/ServiceConfiguration.php
Normal file
7
src/Services/ServiceConfiguration.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Services;
|
||||
|
||||
abstract class ServiceConfiguration
|
||||
{
|
||||
}
|
78
src/Utils/Array.php
Normal file
78
src/Utils/Array.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Utils;
|
||||
|
||||
/**
|
||||
* Get first element of an array.
|
||||
* @template Element
|
||||
* @param Element[] $arr The array on which to get the first element.
|
||||
* @return Element|null First element of the array. NULL if array is empty.
|
||||
*/
|
||||
function array_first(array $arr): mixed
|
||||
{
|
||||
// Try to get first element key.
|
||||
if (!is_null($firstKey = array_key_first($arr)))
|
||||
// There is a first element, returning it.
|
||||
return $arr[$firstKey];
|
||||
else
|
||||
// No first element, returning null.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first element of an array if it's one, or the direct value.
|
||||
* @template Element
|
||||
* @param Element|Element[] $arr_or_val Array or value.
|
||||
* @return Element Value or array first value.
|
||||
*/
|
||||
function array_first_or_val(mixed $arr_or_val): mixed
|
||||
{
|
||||
return is_array($arr_or_val) ? array_first($arr_or_val) : $arr_or_val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten arrays in one array.
|
||||
* @template Element
|
||||
* @param Element[] $items Array of arrays to flatten.
|
||||
* @return Element[] Flattened array.
|
||||
*/
|
||||
function array_flatten(array $items): array
|
||||
{
|
||||
return array_reduce($items, function ($carry, $item) {
|
||||
return array_merge($carry, is_array($item) ? array_flatten($item) : [$item]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate values in an array, but faster than array_unique.
|
||||
* Warning: duplicated values keys may be lost.
|
||||
*
|
||||
* @template Element
|
||||
* @param Element[] $array Array to deduplicate.
|
||||
* @return Element[] Deduplicated array.
|
||||
*/
|
||||
function array_unique_quick(array $array): array
|
||||
{
|
||||
return array_flip(array_flip($array));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether at least one element in the array passes the test implemented by the provided function.
|
||||
* It doesn't modify the array.
|
||||
* @template Element
|
||||
* @param callable $test_function - The function that implements the test.
|
||||
* @param Element[] $array - The array to test.
|
||||
* @return bool - True if it finds an element of the array for which the provided function returns true; returns false otherwise.
|
||||
*/
|
||||
function array_some(callable $test_function, array $array): bool
|
||||
{
|
||||
foreach ($array as $key => $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.
|
||||
}
|
||||
|
13
src/Utils/Paths.php
Normal file
13
src/Utils/Paths.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Utils;
|
||||
|
||||
/**
|
||||
* Join path parts using directory separator.
|
||||
* @param string ...$pathParts Path parts to join.
|
||||
* @return string A single joined path.
|
||||
*/
|
||||
function path_join(string ...$pathParts): string
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, $pathParts);
|
||||
}
|
48
src/Utils/Reflection.php
Normal file
48
src/Utils/Reflection.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Utils;
|
||||
|
||||
use Nest\Exceptions\InvalidTypeException;
|
||||
|
||||
/**
|
||||
* Extract the class name from its full name (\Namespace\Classname).
|
||||
* @param string $class_fullname Full name of the class, with its namespace.
|
||||
* @return string The class name.
|
||||
*/
|
||||
function get_class_name_from_fullname(string $class_fullname): string
|
||||
{ // Keep last part of the string, after the last '\'.
|
||||
return substr($class_fullname, mb_strrpos($class_fullname, "\\") + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given object or class is of the expected type.
|
||||
* @template T
|
||||
* @param string|object $object_or_class Object or class to check.
|
||||
* @param class-string<T> $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) : [])];
|
||||
}
|
82
src/Utils/String.php
Normal file
82
src/Utils/String.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace Nest\Utils;
|
||||
|
||||
/**
|
||||
* Convert a camelCase string in a snake_case string.
|
||||
* @param string $camel_cased_str The camelCase string to convert.
|
||||
* @return string The converted string in snake_case.
|
||||
*/
|
||||
function str_camel_to_snake(string $camel_cased_str): string
|
||||
{
|
||||
return ltrim(strtolower(preg_replace("/[A-Z]([A-Z](?![a-z]))*/", "_$0", $camel_cased_str)), "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plural form of every word in the snake_cased string.
|
||||
* @param string $snake_name_to_pluralize The snake_cased string to pluralize.
|
||||
* @return string The pluralized snake_cased string.
|
||||
*/
|
||||
function str_snake_pluralize(string $snake_name_to_pluralize): string
|
||||
{
|
||||
// Break the string to an array containing each word.
|
||||
$parts = explode("_", $snake_name_to_pluralize);
|
||||
// Add an 's' to each word.
|
||||
$parts = array_map(fn (string $part) => "{$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.
|
||||
}
|
Loading…
Add table
Reference in a new issue