From e1ad5f50893119a4725156f4d20f278fed6418cb Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Sat, 14 Sep 2024 09:47:55 +0200 Subject: [PATCH] First Commit --- .gitignore | 5 + composer.json | 61 ++++ monorepo-builder.php | 9 + src/PHPNative/Container/composer.json | 17 + src/PHPNative/Container/src/Container.php | 33 ++ src/PHPNative/Container/src/ContainerLog.php | 25 ++ src/PHPNative/Container/src/Context.php | 84 +++++ src/PHPNative/Container/src/Dependency.php | 87 +++++ .../Container/src/DynamicInitializer.php | 12 + .../CannotInstantiateDependencyException.php | 56 +++ .../Container/src/GenericContainer.php | 336 ++++++++++++++++++ src/PHPNative/Container/src/HasInstance.php | 20 ++ .../Container/src/InMemoryContainerLog.php | 67 ++++ src/PHPNative/Container/src/Initializer.php | 10 + src/PHPNative/Container/src/Singleton.php | 12 + src/PHPNative/Core/composer.json | 17 + src/PHPNative/Core/src/Window.php | 12 + src/PHPNative/Framework/composer.json | 17 + .../Framework/src/Application/Kernel.php | 60 ++++ .../Framework/src/Application/Window.php | 10 + .../Framework/src/Discovery/Discovery.php | 21 ++ .../src/Discovery/DiscoveryDiscovery.php | 54 +++ .../src/Discovery/DiscoveryLocation.php | 14 + .../src/Discovery/InitializerDiscovery.php | 51 +++ src/PHPNative/Framework/src/Loop/Timer.php | 55 +++ src/PHPNative/Framework/src/PHPNative.php | 16 + src/PHPNative/Tailwind/composer.json | 17 + src/PHPNative/Tailwind/src/Data/colors.json | 288 +++++++++++++++ .../Tailwind/src/Parser/Background.php | 21 ++ src/PHPNative/Tailwind/src/Parser/Color.php | 34 ++ src/PHPNative/Tailwind/src/Parser/Padding.php | 52 +++ src/PHPNative/Tailwind/src/Parser/Parser.php | 11 + .../Tailwind/src/Style/Background.php | 13 + src/PHPNative/Tailwind/src/Style/Color.php | 11 + src/PHPNative/Tailwind/src/Style/Padding.php | 11 + src/PHPNative/Tailwind/src/Style/Style.php | 8 + src/PHPNative/Tailwind/src/StyleParser.php | 17 + .../Tailwind/tests/BackgroundTest.php | 22 ++ src/PHPNative/Tailwind/tests/ColorTest.php | 30 ++ src/PHPNative/Tailwind/tests/PaddingTest.php | 89 +++++ src/PHPNative/UI/View.php | 8 + src/PHPNative/UI/Widget.php | 8 + src/PHPNative/UI/composer.json | 17 + src/PHPNative/UI/src/Trait/Action/Click.php | 10 + src/PHPNative/UI/src/Trait/Style.php | 10 + src/PHPNative/UI/src/View.php | 10 + src/PHPNative/UI/src/Widget/Button.php | 13 + src/PHPNative/UI/src/Widget/Container.php | 10 + 48 files changed, 1871 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 monorepo-builder.php create mode 100644 src/PHPNative/Container/composer.json create mode 100644 src/PHPNative/Container/src/Container.php create mode 100644 src/PHPNative/Container/src/ContainerLog.php create mode 100644 src/PHPNative/Container/src/Context.php create mode 100644 src/PHPNative/Container/src/Dependency.php create mode 100644 src/PHPNative/Container/src/DynamicInitializer.php create mode 100644 src/PHPNative/Container/src/Exceptions/CannotInstantiateDependencyException.php create mode 100644 src/PHPNative/Container/src/GenericContainer.php create mode 100644 src/PHPNative/Container/src/HasInstance.php create mode 100644 src/PHPNative/Container/src/InMemoryContainerLog.php create mode 100644 src/PHPNative/Container/src/Initializer.php create mode 100644 src/PHPNative/Container/src/Singleton.php create mode 100644 src/PHPNative/Core/composer.json create mode 100644 src/PHPNative/Core/src/Window.php create mode 100644 src/PHPNative/Framework/composer.json create mode 100644 src/PHPNative/Framework/src/Application/Kernel.php create mode 100644 src/PHPNative/Framework/src/Application/Window.php create mode 100644 src/PHPNative/Framework/src/Discovery/Discovery.php create mode 100644 src/PHPNative/Framework/src/Discovery/DiscoveryDiscovery.php create mode 100644 src/PHPNative/Framework/src/Discovery/DiscoveryLocation.php create mode 100644 src/PHPNative/Framework/src/Discovery/InitializerDiscovery.php create mode 100644 src/PHPNative/Framework/src/Loop/Timer.php create mode 100644 src/PHPNative/Framework/src/PHPNative.php create mode 100644 src/PHPNative/Tailwind/composer.json create mode 100644 src/PHPNative/Tailwind/src/Data/colors.json create mode 100644 src/PHPNative/Tailwind/src/Parser/Background.php create mode 100644 src/PHPNative/Tailwind/src/Parser/Color.php create mode 100644 src/PHPNative/Tailwind/src/Parser/Padding.php create mode 100644 src/PHPNative/Tailwind/src/Parser/Parser.php create mode 100644 src/PHPNative/Tailwind/src/Style/Background.php create mode 100644 src/PHPNative/Tailwind/src/Style/Color.php create mode 100644 src/PHPNative/Tailwind/src/Style/Padding.php create mode 100644 src/PHPNative/Tailwind/src/Style/Style.php create mode 100644 src/PHPNative/Tailwind/src/StyleParser.php create mode 100644 src/PHPNative/Tailwind/tests/BackgroundTest.php create mode 100644 src/PHPNative/Tailwind/tests/ColorTest.php create mode 100644 src/PHPNative/Tailwind/tests/PaddingTest.php create mode 100644 src/PHPNative/UI/View.php create mode 100644 src/PHPNative/UI/Widget.php create mode 100644 src/PHPNative/UI/composer.json create mode 100644 src/PHPNative/UI/src/Trait/Action/Click.php create mode 100644 src/PHPNative/UI/src/Trait/Style.php create mode 100644 src/PHPNative/UI/src/View.php create mode 100644 src/PHPNative/UI/src/Widget/Button.php create mode 100644 src/PHPNative/UI/src/Widget/Container.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3180570 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cache/ +.idea/ +build/ +vendor/ +composer.lock diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..483ad59 --- /dev/null +++ b/composer.json @@ -0,0 +1,61 @@ +{ + "name": "phpnative/framework", + "type": "library", + "license": "MIT", + "autoload": { + "psr-4": { + "PHPNative\\Framework\\": "src/PHPNative/Framework/src", + "PHPNative\\UI\\": "src/PHPNative/UI/src", + "PHPNative\\Tailwind\\": "src/PHPNative/Tailwind/src", + "PHPNative\\Core\\": "src/PHPNative/Core/src", + "PHPNative\\Container\\": "src/PHPNative/Container/src" + } + }, + "replace": { + "phpnative/tailwind": "self.version" + }, + "autoload-dev": { + "psr-4": { + "PHPNative\\Tailwind\\Tests\\": "src/PHPNative/Tailwind/tests", + "PHPNative\\Core\\Tests\\": "src/PHPNative/Core/tests", + "PHPNative\\UI\\Tests\\": "src/PHPNative/UI/tests", + "PHPNative\\Container\\Tests\\": "src/PHPNative/Container/tests", + "PHPNative\\Framework\\Tests\\": "src/PHPNative/Framework/tests" + } + }, + "authors": [ + { + "name": "Thomas Peterson", + "email": "info@thomas-peterson.de" + } + ], + "require": { + "ext-sdl": "*", + "ext-parallel": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.21", + "phpat/phpat": "^0.10.14", + "phpstan/phpstan": "^1.10.0", + "phpunit/phpunit": "^10.2", + "rector/rector": "^1.2", + "spaze/phpstan-disallowed-calls": "^3.1", + "symplify/monorepo-builder": "^11.2" + }, + "scripts": { + "phpunit": "vendor/bin/phpunit --display-warnings --display-skipped --display-deprecations --display-errors --display-notices", + "coverage": "vendor/bin/phpunit --coverage-html build/reports/html --coverage-clover build/reports/clover.xml", + "csfixer": "vendor/bin/php-cs-fixer fix --allow-risky=yes", + "phpstan": "vendor/bin/phpstan analyse src tests --memory-limit=1G", + "rector": "vendor/bin/rector process --no-ansi", + "merge": "vendor/bin/monorepo-builder merge", + "qa": [ + "composer merge", + "./tempest discovery:clear", + "vendor/bin/rector process", + "composer csfixer", + "composer phpunit", + "composer phpstan" + ] + } +} diff --git a/monorepo-builder.php b/monorepo-builder.php new file mode 100644 index 0000000..e0d3e5e --- /dev/null +++ b/monorepo-builder.php @@ -0,0 +1,9 @@ +packageDirectories([__DIR__ . '/src/PHPNative']); +}; diff --git a/src/PHPNative/Container/composer.json b/src/PHPNative/Container/composer.json new file mode 100644 index 0000000..99906dc --- /dev/null +++ b/src/PHPNative/Container/composer.json @@ -0,0 +1,17 @@ +{ + "name": "phpnative/container", + "license": "MIT", + "require": { + "php": "^8.3" + }, + "autoload": { + "psr-4": { + "PHPNative\\Container\\": "src" + } +}, + "autoload-dev": { + "psr-4": { + "PHPNative\\Container\\Tests\\": "tests" + } +} +} \ No newline at end of file diff --git a/src/PHPNative/Container/src/Container.php b/src/PHPNative/Container/src/Container.php new file mode 100644 index 0000000..26b11da --- /dev/null +++ b/src/PHPNative/Container/src/Container.php @@ -0,0 +1,33 @@ + $className + * @return TClassName + */ + public function get(string $className, mixed ...$params): object; + + public function call(object $object, string $methodName, mixed ...$params): mixed; + + /** + * @template T of \PHPNative\Container\src\Initializer + * @template U of \PHPNative\Container\src\DynamicInitializer + * @param ReflectionClass|class-string|class-string $initializerClass + * @return self + */ + public function addInitializer(ReflectionClass|string $initializerClass): self; +} \ No newline at end of file diff --git a/src/PHPNative/Container/src/ContainerLog.php b/src/PHPNative/Container/src/ContainerLog.php new file mode 100644 index 0000000..01d48e3 --- /dev/null +++ b/src/PHPNative/Container/src/ContainerLog.php @@ -0,0 +1,25 @@ +dependencies[] = $dependency; + + return $this; + } + + public function currentDependency(): ?Dependency + { + return $this->dependencies[array_key_last($this->dependencies)] ?? null; + } + + public function getName(): string + { + return match($this->reflector::class) { + ReflectionClass::class => $this->reflector->getName(), + ReflectionMethod::class => $this->reflector->getDeclaringClass()->getName(), + ReflectionFunction::class => $this->reflector->getName() . ' in ' . $this->reflector->getFileName() . ':' . $this->reflector->getStartLine(), + }; + } + + public function getShortName(): string + { + return match($this->reflector::class) { + ReflectionClass::class => $this->reflector->getShortName(), + ReflectionMethod::class => $this->reflector->getDeclaringClass()->getShortName(), + ReflectionFunction::class => $this->reflector->getShortName() . ' in ' . $this->reflector->getFileName() . ':' . $this->reflector->getStartLine(), + }; + } + + public function __toString(): string + { + return match($this->reflector::class) { + ReflectionClass::class => $this->classToString(), + ReflectionMethod::class => $this->methodToString(), + ReflectionFunction::class => $this->functionToString(), + }; + } + + private function classToString(): string + { + return $this->reflector->getShortName(); + } + + private function methodToString(): string + { + return $this->reflector->getDeclaringClass()->getShortName() . '::' . $this->reflector->getName() . '(' . $this->dependenciesToString() . ')'; + } + + private function functionToString(): string + { + return $this->reflector->getShortName() . ' in ' . $this->reflector->getFileName() . ':' . $this->reflector->getStartLine() . '(' . $this->dependenciesToString() . ')'; + } + + private function dependenciesToString(): string + { + return implode( + ', ', + array_map( + fn (Dependency $dependency) => (string) $dependency, + $this->dependencies, + ) + ); + } +} diff --git a/src/PHPNative/Container/src/Dependency.php b/src/PHPNative/Container/src/Dependency.php new file mode 100644 index 0000000..9f770b4 --- /dev/null +++ b/src/PHPNative/Container/src/Dependency.php @@ -0,0 +1,87 @@ +typeToString($this->getType()); + } + + public function __toString(): string + { + $typeToString = $this->typeToString($this->getType()); + $parts = explode('\\', $typeToString); + $typeToString = $parts[array_key_last($parts)]; + + return implode( + ' ', + array_filter([ + $typeToString, + '$' . $this->reflector->getName(), + ]), + ); + } + + private function getType(): string|ReflectionType + { + return match($this->reflector::class) { + ReflectionParameter::class => $this->reflector->getType(), + ReflectionClass::class => $this->reflector->getName(), + }; + } + + private function typeToString(string|ReflectionType|null $type): ?string + { + if ($type === null) { + return null; + } + + if (is_string($type)) { + return $type; + } + + return match($type::class) { + ReflectionIntersectionType::class => $this->intersectionTypeToString($type), + ReflectionNamedType::class => $type->getName(), + ReflectionUnionType::class => $this->unionTypeToString($type), + }; + } + + private function intersectionTypeToString(ReflectionIntersectionType $type): string + { + return implode( + '&', + array_map( + fn (ReflectionType $subType) => $this->typeToString($subType), + $type->getTypes(), + ), + ); + } + + private function unionTypeToString(ReflectionUnionType $type): string + { + return implode( + '|', + array_map( + fn (ReflectionType $subType) => $this->typeToString($subType), + $type->getTypes(), + ), + ); + } +} diff --git a/src/PHPNative/Container/src/DynamicInitializer.php b/src/PHPNative/Container/src/DynamicInitializer.php new file mode 100644 index 0000000..fcc7b28 --- /dev/null +++ b/src/PHPNative/Container/src/DynamicInitializer.php @@ -0,0 +1,12 @@ +getName()} because it is not an instantiable class. Maybe it's missing an initializer class?" . PHP_EOL; + + $stack = $containerLog->getStack(); + + if ($stack === []) { + parent::__construct($message); + + return; + } + + $lastContext = $stack[array_key_last($stack)]; + + $i = 0; + + foreach ($stack as $currentContext) { + $pipe = match (true) { + count($stack) > 1 && $i === 0 => '┌──', + count($stack) > 1 && $i === count($stack) - 1 => '└──', + count($stack) === 1 => ' ', + default => '├──', + }; + + $message .= PHP_EOL . "\t{$pipe} " . $currentContext; + + $i++; + } + + $currentDependency = $lastContext->currentDependency(); + $currentDependencyName = (string)$currentDependency; + $firstPart = explode($currentDependencyName, (string)$lastContext)[0] ?? null; + $fillerSpaces = str_repeat(' ', strlen($firstPart) + 3); + $fillerArrows = str_repeat('▒', strlen($currentDependencyName)); + $message .= PHP_EOL . "\t {$fillerSpaces}{$fillerArrows}"; + + $message .= PHP_EOL . PHP_EOL; + + $message .= "Originally called in {$containerLog->getOrigin()}"; + $message .= PHP_EOL; + + parent::__construct($message); + } +} diff --git a/src/PHPNative/Container/src/GenericContainer.php b/src/PHPNative/Container/src/GenericContainer.php new file mode 100644 index 0000000..efe558c --- /dev/null +++ b/src/PHPNative/Container/src/GenericContainer.php @@ -0,0 +1,336 @@ + $initializers + */ + private array $initializers = [], + + /** + * @template T of \PHPNative\Container\src\DynamicInitializer + * @var class-string $dynamicInitializers + */ + private array $dynamicInitializers = [], + private readonly ContainerLog $log = new InMemoryContainerLog(), + ) { + } + + public function setInitializers(array $initializers): void + { + $this->initializers = $initializers; + } + + public function getInitializers(): array + { + return $this->initializers; + } + + public function register(string $className, callable $definition): self + { + $this->definitions[$className] = $definition; + + return $this; + } + + public function singleton(string $className, callable $definition): self + { + $this->definitions[$className] = function () use ($definition, $className) { + $instance = $definition($this); + + $this->singletons[$className] = $instance; + + return $instance; + }; + + return $this; + } + + public function config(object $config): self + { + $this->singleton($config::class, fn () => $config); + + return $this; + } + + public function get(string $className, mixed ...$params): object + { + $this->log->startResolving(); + + return $this->resolve($className, ...$params); + } + + public function call(string|object $object, string $methodName, ...$params): mixed + { + $this->log->startResolving(); + + $object = is_string($object) ? $this->get($object) : $object; + + $reflectionMethod = (new ReflectionClass($object))->getMethod($methodName); + + $parameters = $this->autowireDependencies($reflectionMethod, $params); + + return $reflectionMethod->invokeArgs($object, $parameters); + } + + public function addInitializer(ReflectionClass|string $initializerClass): Container + { + $initializerClass = $initializerClass instanceof ReflectionClass + ? $initializerClass + : new ReflectionClass($initializerClass); + + // First, we check whether this is a DynamicInitializer, + // which don't have a one-to-one mapping + if ($initializerClass->implementsInterface(DynamicInitializer::class)) { + $this->dynamicInitializers[] = $initializerClass->getName(); + + return $this; + } + + // For normal Initializers, we'll use the return type + // to determine which dependency they resolve + $returnTypes = $initializerClass->getMethod('initialize')->getReturnType(); + + $returnTypes = match ($returnTypes::class) { + ReflectionNamedType::class => [$returnTypes], + ReflectionUnionType::class, ReflectionIntersectionType::class => $returnTypes->getTypes(), + }; + + /** @var ReflectionNamedType[] $returnTypes */ + foreach ($returnTypes as $returnType) { + $this->initializers[$returnType->getName()] = $initializerClass->getName(); + } + + return $this; + } + + private function resolve(string $className, mixed ...$params): object + { + // Check if the class has been registered as a singleton. + if ($instance = $this->singletons[$className] ?? null) { + $this->log->addContext(new Context(new ReflectionClass($className))); + + return $instance; + } + + // Check if a callable has been registered to resolve this class. + if ($definition = $this->definitions[$className] ?? null) { + $this->log->addContext(new Context(new ReflectionFunction($definition))); + + return $definition($this); + } + + // Next we check if any of our default initializers can initialize this class. + // If there's an initializer, we don't keep track of the log anymore, + // since initializers are outside the container's responsibility. + if ($initializer = $this->initializerFor($className)) { + $object = match (true) { + $initializer instanceof Initializer => $initializer->initialize($this), + $initializer instanceof DynamicInitializer => $initializer->initialize($className, $this), + }; + // Check whether the initializer's result should be registered as a singleton + if (attribute(Singleton::class)->in($initializer::class)->first() !== null) { + $this->singleton($className, fn () => $object); + + return $this->get($className); + } + + return $object; + } + + // Finally, autowire the class. + return $this->autowire($className, ...$params); + } + + private function initializerFor(string $className): null|Initializer|DynamicInitializer + { + // Initializers themselves can't be initialized, + // otherwise you'd end up with infinite loops + if ( + is_a($className, Initializer::class, true) + || is_a($className, DynamicInitializer::class, true) + ) { + return null; + } + + if ($initializerClass = $this->initializers[$className] ?? null) { + return $this->resolve($initializerClass); + } + + // Loop through the registered initializers to see if + // we have something to handle this class. + foreach ($this->dynamicInitializers as $initializerClass) { + $initializer = $this->resolve($initializerClass); + + if (! $initializer->canInitialize($className)) { + continue; + } + + return $initializer; + } + + return null; + } + + private function autowire(string $className, mixed ...$params): object + { + $reflectionClass = new ReflectionClass($className); + + $constructor = $reflectionClass->getConstructor(); + + if (! $reflectionClass->isInstantiable()) { + throw new CannotInstantiateDependencyException($reflectionClass, $this->log); + } + + return $constructor === null + // If there isn't a constructor, don't waste time + // trying to build it. + ? $reflectionClass->newInstanceWithoutConstructor() + + // Otherwise, use our autowireDependencies helper to automagically + // build up each parameter. + : $reflectionClass->newInstanceArgs( + $this->autowireDependencies($constructor, $params), + ); + } + + /** + * @return ReflectionParameter[] + */ + private function autowireDependencies(ReflectionMethod $method, array $parameters = []): array + { + $this->log->addContext(new Context($method)); + + $dependencies = []; + + // Build the class by iterating through its + // dependencies and resolving them. + foreach ($method->getParameters() as $parameter) { + $dependencies[] = $this->autowireDependency( + parameter: $parameter, + providedValue: $parameters[$parameter->getName()] ?? null, + ); + } + + return $dependencies; + } + + private function autowireDependency(ReflectionParameter $parameter, mixed $providedValue = null): mixed + { + $this->log->addDependency(new Dependency($parameter)); + + $parameterType = $parameter->getType(); + + // If the parameter is a built-in type, immediately skip reflection + // stuff and attempt to give it a default or null value. + if ($parameterType instanceof ReflectionNamedType && $parameterType->isBuiltin()) { + return $this->autowireBuiltinDependency($parameter, $providedValue); + } + + // Convert the types to an array regardless, so we can handle + // union types and single types the same. + $types = match ($parameterType::class) { + ReflectionNamedType::class => [$parameterType], + ReflectionUnionType::class, ReflectionIntersectionType::class => $parameterType->getTypes(), + }; + + // Loop through each type until we hit a match. + foreach ($types as $type) { + try { + return $this->autowireObjectDependency($type, $providedValue); + } catch (Throwable $throwable) { + // We were unable to resolve the dependency for the last union + // type, so we are moving on to the next one. We hang onto + // the exception in case it is a circular reference. + $lastThrowable = $throwable; + } + } + + // If the dependency has a default value, we do our best to prevent + // an error by using that. + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + // At this point, there is nothing else we can do; we don't know + // how to autowire this dependency. + throw $lastThrowable ?? new CannotAutowireException($this->log); + } + + private function autowireObjectDependency(ReflectionNamedType $type, mixed $providedValue): mixed + { + // If the provided value is of the right type, + // don't waste time autowiring, return it! + if (is_a($providedValue, $type->getName())) { + return $providedValue; + } + + // If we can successfully retrieve an instance + // of the necessary dependency, return it. + if ($instance = $this->resolve($type->getName())) { + return $instance; + } + + // At this point, there is nothing else we can do; we don't know + // how to autowire this dependency. + throw new CannotAutowireException($this->log); + } + + private function autowireBuiltinDependency(ReflectionParameter $parameter, mixed $providedValue): mixed + { + // Due to type coercion, the provided value may (or may not) work. + // Here we give up trying to do type work for people. If they + // didn't provide the right type, that's on them. + if ($providedValue !== null) { + return $providedValue; + } + + // If the dependency has a default value, we might as well + // use that at this point. + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + // If the dependency's type is an array or variadic variable, we'll + // try to prevent an error by returning an empty array. + if ( + $parameter->getType()?->getName() === 'array' || + $parameter->isVariadic() + ) { + return []; + } + + // If the dependency's type allows null or is optional, we'll + // try to prevent an error by returning null. + if ($parameter->allowsNull() || $parameter->isOptional()) { + return null; + } + + // At this point, there is nothing else we can do; we don't know + // how to autowire this dependency. + throw new CannotAutowireException($this->log); + } +} \ No newline at end of file diff --git a/src/PHPNative/Container/src/HasInstance.php b/src/PHPNative/Container/src/HasInstance.php new file mode 100644 index 0000000..7fdb6c5 --- /dev/null +++ b/src/PHPNative/Container/src/HasInstance.php @@ -0,0 +1,20 @@ +origin = $trace[1]['file'] . ':' . $trace[1]['line']; + $this->stack = []; + + return $this; + } + + public function addContext(Context $context): ContainerLog + { + if (isset($this->stack[$context->getName()])) { + throw new CircularDependencyException($this, $context); + } + + $this->stack[$context->getName()] = $context; + + return $this; + } + + public function addDependency(Dependency $dependency): ContainerLog + { + $this->currentContext()->addDependency($dependency); + + return $this; + } + + public function getStack(): array + { + return $this->stack; + } + + public function currentContext(): Context + { + return $this->stack[array_key_last($this->stack)] + ?? throw new Exception("No current context found. That shoudn't happen. Aidan probably wrote a bug somewhere."); + } + + public function currentDependency(): ?Dependency + { + return $this->currentContext()->currentDependency(); + } + + public function getOrigin(): string + { + return $this->origin; + } +} diff --git a/src/PHPNative/Container/src/Initializer.php b/src/PHPNative/Container/src/Initializer.php new file mode 100644 index 0000000..bc3f1d3 --- /dev/null +++ b/src/PHPNative/Container/src/Initializer.php @@ -0,0 +1,10 @@ +init(); + } + + public function init(): void + { + $container = $this->createContainer(); + + $bootstraps = [ + DiscoveryLocationBootstrap::class, + ConfigBootstrap::class, + DiscoveryBootstrap::class, + ]; + + foreach ($bootstraps as $bootstrap) { + $container->get( + $bootstrap, + kernel: $this, + )->boot(); + } + + return $container; + } + + private function createContainer(): Container + { + $container = new GenericContainer(); + + GenericContainer::setInstance($container); + + $container + ->singleton(self::class, fn () => $this) + ->singleton(Container::class, fn () => $container) + ; + return $container; + } + +} diff --git a/src/PHPNative/Framework/src/Application/Window.php b/src/PHPNative/Framework/src/Application/Window.php new file mode 100644 index 0000000..ec912ea --- /dev/null +++ b/src/PHPNative/Framework/src/Application/Window.php @@ -0,0 +1,10 @@ +isInstantiable() + || ! $class->implementsInterface(Discovery::class) + || $class->getName() === self::class + ) { + return; + } + + $this->appConfig->discoveryClasses[] = $class->getName(); + } + + public function hasCache(): bool + { + return file_exists(self::CACHE_PATH); + } + + public function storeCache(): void + { + file_put_contents(self::CACHE_PATH, serialize($this->appConfig->discoveryClasses)); + } + + public function restoreCache(Container $container): void + { + $discoveryClasses = unserialize(file_get_contents(self::CACHE_PATH)); + + $this->appConfig->discoveryClasses = $discoveryClasses; + } + + public function destroyCache(): void + { + @unlink(self::CACHE_PATH); + } +} diff --git a/src/PHPNative/Framework/src/Discovery/DiscoveryLocation.php b/src/PHPNative/Framework/src/Discovery/DiscoveryLocation.php new file mode 100644 index 0000000..b513c2d --- /dev/null +++ b/src/PHPNative/Framework/src/Discovery/DiscoveryLocation.php @@ -0,0 +1,14 @@ +isInstantiable() + || ! $class->implementsInterface(Initializer::class) + ) { + return; + } + + $this->container->addInitializer($class); + } + + public function hasCache(): bool + { + return file_exists(self::CACHE_PATH); + } + + public function storeCache(): void + { + file_put_contents(self::CACHE_PATH, serialize($this->container->getInitializers())); + } + + public function restoreCache(Container $container): void + { + $initializers = unserialize(file_get_contents(self::CACHE_PATH)); + + $this->container->setInitializers($initializers); + } + + public function destroyCache(): void + { + @unlink(self::CACHE_PATH); + } +} \ No newline at end of file diff --git a/src/PHPNative/Framework/src/Loop/Timer.php b/src/PHPNative/Framework/src/Loop/Timer.php new file mode 100644 index 0000000..898d671 --- /dev/null +++ b/src/PHPNative/Framework/src/Loop/Timer.php @@ -0,0 +1,55 @@ +rate($rate); + } + + public function touch(float $now = null): void + { + $this->time = $now ?? \microtime(true); + } + + + public function rate(int $rate): self + { + $this->rate = $rate === 0 ? 0 : 1 / $rate; + + return $this; + } + + public function delta(float $now): float + { + return $now - $this->time; + } + + public function next(float $now): ?float + { + $delta = $this->delta($now); + + if ($delta > 0) { + $this->ops = 1 / $delta; + } + + if ($delta > $this->rate) { + $this->time = $now; + + return $delta; + } + + return null; + } +} \ No newline at end of file diff --git a/src/PHPNative/Framework/src/PHPNative.php b/src/PHPNative/Framework/src/PHPNative.php new file mode 100644 index 0000000..bbee542 --- /dev/null +++ b/src/PHPNative/Framework/src/PHPNative.php @@ -0,0 +1,16 @@ + 0) { + $colorStyle = $output_array[1][0]; + $color = Color::parse($colorStyle); + + } + return new \PHPNative\Tailwind\Style\Background($color); + } +} \ No newline at end of file diff --git a/src/PHPNative/Tailwind/src/Parser/Color.php b/src/PHPNative/Tailwind/src/Parser/Color.php new file mode 100644 index 0000000..87906e6 --- /dev/null +++ b/src/PHPNative/Tailwind/src/Parser/Color.php @@ -0,0 +1,34 @@ + 0) { + $color = (string)$output_array[1][0]; + list($red, $green, $blue) = sscanf($data[$color]['500'], "#%02x%02x%02x"); + } + + preg_match_all('/(\w{1,8})-(\d{1,3})/', $style, $output_array); + if(count($output_array[0]) > 0) { + $color = (string)$output_array[1][0]; + $variant = (string)$output_array[2][0]; + list($red, $green, $blue) = sscanf($data[$color][$variant], "#%02x%02x%02x"); + } + + + + return new \PHPNative\Tailwind\Style\Color($red, $green, $blue); + } +} \ No newline at end of file diff --git a/src/PHPNative/Tailwind/src/Parser/Padding.php b/src/PHPNative/Tailwind/src/Parser/Padding.php new file mode 100644 index 0000000..5941226 --- /dev/null +++ b/src/PHPNative/Tailwind/src/Parser/Padding.php @@ -0,0 +1,52 @@ + 0) { + $l = (int)$output_array[1][0]; + $r = (int)$output_array[1][0]; + $t = (int)$output_array[1][0]; + $b = (int)$output_array[1][0]; + } + preg_match_all('/px-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $l = (int)$output_array[1][0]; + $r = (int)$output_array[1][0]; + } + preg_match_all('/py-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $t = (int)$output_array[1][0]; + $b = (int)$output_array[1][0]; + } + preg_match_all('/pt-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $t = (int)$output_array[1][0]; + } + preg_match_all('/pb-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $b = (int)$output_array[1][0]; + } + preg_match_all('/pl-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $l = (int)$output_array[1][0]; + } + preg_match_all('/pr-(\d)/', $style, $output_array); + if(count($output_array[0]) > 0) { + $r = (int)$output_array[1][0]; + } + + return new \PHPNative\Tailwind\Style\Padding($l, $r, $t, $b); + } +} \ No newline at end of file diff --git a/src/PHPNative/Tailwind/src/Parser/Parser.php b/src/PHPNative/Tailwind/src/Parser/Parser.php new file mode 100644 index 0000000..982dd1f --- /dev/null +++ b/src/PHPNative/Tailwind/src/Parser/Parser.php @@ -0,0 +1,11 @@ +assertInstanceOf(\PHPNative\Tailwind\Style\Background::class, $bg); + $this->assertSame(203, $bg->color->red); + $this->assertSame(213, $bg->color->green); + $this->assertSame(225, $bg->color->blue); + $this->assertSame(0, $bg->color->alpha); + } + +} \ No newline at end of file diff --git a/src/PHPNative/Tailwind/tests/ColorTest.php b/src/PHPNative/Tailwind/tests/ColorTest.php new file mode 100644 index 0000000..10c0c51 --- /dev/null +++ b/src/PHPNative/Tailwind/tests/ColorTest.php @@ -0,0 +1,30 @@ +red); + self::assertSame(68, $color->green); + self::assertSame(68, $color->blue); + self::assertSame(0, $color->alpha); + } + + public function testNameVariant(): void + { + $color = Color::parse("lime-300"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color); + self::assertSame(190, $color->red); + self::assertSame(242, $color->green); + self::assertSame(100, $color->blue); + self::assertSame(0, $color->alpha); + } +} \ No newline at end of file diff --git a/src/PHPNative/Tailwind/tests/PaddingTest.php b/src/PHPNative/Tailwind/tests/PaddingTest.php new file mode 100644 index 0000000..513bb5c --- /dev/null +++ b/src/PHPNative/Tailwind/tests/PaddingTest.php @@ -0,0 +1,89 @@ +left); + self::assertSame(6, $padding->right); + self::assertSame(6, $padding->top); + self::assertSame(6, $padding->bottom); + } + + public function testPaddingX(): void + { + $padding = Padding::parse("px-2"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(2, $padding->left); + self::assertSame(2, $padding->right); + self::assertSame(0, $padding->top); + self::assertSame(0, $padding->bottom); + } + + public function testPaddingY(): void + { + $padding = Padding::parse("py-2"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(0, $padding->left); + self::assertSame(0, $padding->right); + self::assertSame(2, $padding->top); + self::assertSame(2, $padding->bottom); + } + + public function testPaddingT(): void + { + $padding = Padding::parse("pt-3"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(0, $padding->left); + self::assertSame(0, $padding->right); + self::assertSame(3, $padding->top); + self::assertSame(0, $padding->bottom); + } + + public function testPaddingB(): void + { + $padding = Padding::parse("pb-3"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(0, $padding->left); + self::assertSame(0, $padding->right); + self::assertSame(0, $padding->top); + self::assertSame(3, $padding->bottom); + } + + public function testPaddingL(): void + { + $padding = Padding::parse("pl-3"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(3, $padding->left); + self::assertSame(0, $padding->right); + self::assertSame(0, $padding->top); + self::assertSame(0, $padding->bottom); + } + + public function testPaddingR(): void + { + $padding = Padding::parse("pr-3"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(0, $padding->left); + self::assertSame(3, $padding->right); + self::assertSame(0, $padding->top); + self::assertSame(0, $padding->bottom); + } + + public function testPaddingComplex(): void + { + $padding = Padding::parse("p-2 pl-1 pr-3"); + self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding); + self::assertSame(1, $padding->left); + self::assertSame(3, $padding->right); + self::assertSame(2, $padding->top); + self::assertSame(2, $padding->bottom); + } +} \ No newline at end of file diff --git a/src/PHPNative/UI/View.php b/src/PHPNative/UI/View.php new file mode 100644 index 0000000..b35e317 --- /dev/null +++ b/src/PHPNative/UI/View.php @@ -0,0 +1,8 @@ +