Backup
This commit is contained in:
parent
e1ad5f5089
commit
7756cb774b
88
.php-cs-fixer.dist.php
Normal file
88
.php-cs-fixer.dist.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$finder = Symfony\Component\Finder\Finder::create()
|
||||
->in([
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
])
|
||||
->name('*.php')
|
||||
->notName('*.cache.php')
|
||||
->ignoreDotFiles(true)
|
||||
->ignoreVCS(true);
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setCacheFile('.cache/fixer/cs-fixer.cache')
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'ordered_imports' => ['sort_algorithm' => 'alpha'],
|
||||
'no_unused_imports' => true,
|
||||
'blank_line_between_import_groups' => false,
|
||||
'single_import_per_statement' => true,
|
||||
'no_leading_import_slash' => true,
|
||||
'no_unneeded_import_alias' => true,
|
||||
'fully_qualified_strict_types' => [
|
||||
'import_symbols' => true,
|
||||
],
|
||||
'global_namespace_import' => [
|
||||
'import_classes' => true,
|
||||
'import_constants' => true,
|
||||
'import_functions' => true,
|
||||
],
|
||||
'not_operator_with_successor_space' => true,
|
||||
'trailing_comma_in_multiline' => true,
|
||||
'phpdoc_scalar' => true,
|
||||
'unary_operator_spaces' => true,
|
||||
'binary_operator_spaces' => true,
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
|
||||
],
|
||||
'phpdoc_single_line_var_spacing' => true,
|
||||
'phpdoc_var_without_name' => true,
|
||||
'class_attributes_separation' => [
|
||||
'elements' => [
|
||||
'method' => 'one',
|
||||
],
|
||||
],
|
||||
'method_argument_space' => [
|
||||
'on_multiline' => 'ensure_fully_multiline',
|
||||
'keep_multiple_spaces_after_comma' => true,
|
||||
],
|
||||
'single_trait_insert_per_statement' => true,
|
||||
'declare_strict_types' => true,
|
||||
'no_empty_comment' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
|
||||
// Test styling
|
||||
'php_unit_data_provider_name' => [
|
||||
'prefix' => 'provide_',
|
||||
'suffix' => '_cases',
|
||||
],
|
||||
'php_unit_data_provider_return_type' => true,
|
||||
'php_unit_data_provider_static' => [
|
||||
'force' => true,
|
||||
],
|
||||
'php_unit_dedicate_assert_internal_type' => true,
|
||||
'php_unit_internal_class' => true,
|
||||
'php_unit_method_casing' => [
|
||||
'case' => 'snake_case',
|
||||
],
|
||||
'php_unit_expectation' => [
|
||||
'target' => 'newest',
|
||||
],
|
||||
'php_unit_mock' => [
|
||||
'target' => 'newest',
|
||||
],
|
||||
'php_unit_mock_short_will_return' => true,
|
||||
'php_unit_set_up_tear_down_visibility' => true,
|
||||
'php_unit_size_class' => true,
|
||||
'php_unit_test_annotation' => [
|
||||
'style' => 'prefix',
|
||||
],
|
||||
'php_unit_test_case_static_method_calls' => [
|
||||
'call_type' => 'this',
|
||||
],
|
||||
])
|
||||
->setFinder($finder);
|
||||
BIN
assets/segoe-ui.ttf
Normal file
BIN
assets/segoe-ui.ttf
Normal file
Binary file not shown.
@ -1,28 +1,7 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"type": "library",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Thomas Peterson",
|
||||
@ -30,8 +9,9 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"ext-parallel": "*",
|
||||
"ext-sdl": "*",
|
||||
"ext-parallel": "*"
|
||||
"php": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.21",
|
||||
@ -42,6 +22,40 @@
|
||||
"spaze/phpstan-disallowed-calls": "^3.1",
|
||||
"symplify/monorepo-builder": "^11.2"
|
||||
},
|
||||
"replace": {
|
||||
"phpnative/container": "self.version",
|
||||
"phpnative/core": "self.version",
|
||||
"phpnative/event": "self.version",
|
||||
"phpnative/framework": "self.version",
|
||||
"phpnative/renderer": "self.version",
|
||||
"phpnative/support": "self.version",
|
||||
"phpnative/tailwind": "self.version",
|
||||
"phpnative/ui": "self.version"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Container\\": "src/PHPNative/Container/src",
|
||||
"PHPNative\\Core\\": "src/PHPNative/Core/src",
|
||||
"PHPNative\\Event\\": "src/PHPNative/Event/src",
|
||||
"PHPNative\\Framework\\": "src/PHPNative/Framework/src",
|
||||
"PHPNative\\Renderer\\": "src/PHPNative/Renderer/src",
|
||||
"PHPNative\\Support\\": "src/PHPNative/Support/src",
|
||||
"PHPNative\\Tailwind\\": "src/PHPNative/Tailwind/src",
|
||||
"PHPNative\\UI\\": "src/PHPNative/UI/src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Container\\Tests\\": "src/PHPNative/Container/tests",
|
||||
"PHPNative\\Core\\Tests\\": "src/PHPNative/Core/tests",
|
||||
"PHPNative\\Event\\Tests\\": "src/PHPNative/Event/tests",
|
||||
"PHPNative\\Framework\\Tests\\": "src/PHPNative/Framework/tests",
|
||||
"PHPNative\\Renderer\\Tests\\": "src/PHPNative/Renderer/tests",
|
||||
"PHPNative\\Support\\Tests\\": "src/PHPNative/Support/tests",
|
||||
"PHPNative\\Tailwind\\Tests\\": "src/PHPNative/Tailwind/tests",
|
||||
"PHPNative\\UI\\Tests\\": "src/PHPNative/UI/tests"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@ -51,7 +65,6 @@
|
||||
"merge": "vendor/bin/monorepo-builder merge",
|
||||
"qa": [
|
||||
"composer merge",
|
||||
"./tempest discovery:clear",
|
||||
"vendor/bin/rector process",
|
||||
"composer csfixer",
|
||||
"composer phpunit",
|
||||
|
||||
75
rector.php
Normal file
75
rector.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector;
|
||||
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
|
||||
use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPublicMethodParameterRector;
|
||||
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
|
||||
use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector;
|
||||
use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector;
|
||||
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
|
||||
use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector;
|
||||
use Rector\Php74\Rector\Ternary\ParenthesizeNestedTernaryRector;
|
||||
use Rector\Php81\Rector\Array_\FirstClassCallableRector;
|
||||
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
|
||||
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
|
||||
use Rector\Php82\Rector\Class_\ReadOnlyClassRector;
|
||||
use Rector\Php82\Rector\Param\AddSensitiveParameterAttributeRector;
|
||||
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
|
||||
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;
|
||||
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
|
||||
use Rector\TypeDeclaration\Rector\Closure\ClosureReturnTypeRector;
|
||||
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withCache('./.cache/rector', FileCacheStorage::class)
|
||||
->withPaths([
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
])
|
||||
->withConfiguredRule(AddSensitiveParameterAttributeRector::class, [
|
||||
'sensitive_parameters' => [
|
||||
'password',
|
||||
'secret',
|
||||
],
|
||||
])
|
||||
->withRules([
|
||||
ParenthesizeNestedTernaryRector::class,
|
||||
ExplicitNullableParamTypeRector::class,
|
||||
])
|
||||
->withSkip([
|
||||
AddOverrideAttributeToOverriddenMethodsRector::class,
|
||||
ArgumentAdderRector::class,
|
||||
ClosureToArrowFunctionRector::class,
|
||||
EmptyOnNullableObjectToInstanceOfRector::class,
|
||||
FirstClassCallableRector::class,
|
||||
NullToStrictStringFuncCallArgRector::class,
|
||||
ReadOnlyClassRector::class,
|
||||
ReadOnlyPropertyRector::class,
|
||||
RemoveNullPropertyInitializationRector::class,
|
||||
RemoveUnreachableStatementRector::class,
|
||||
AddSensitiveParameterAttributeRector::class,
|
||||
RemoveUnusedPublicMethodParameterRector::class,
|
||||
RestoreDefaultNullToNullableTypePropertyRector::class,
|
||||
ReturnNeverTypeRector::class,
|
||||
StaticCallOnNonStaticToInstanceCallRector::class,
|
||||
ClosureReturnTypeRector::class,
|
||||
EncapsedStringsToSprintfRector::class,
|
||||
AddArrowFunctionReturnTypeRector::class,
|
||||
])
|
||||
->withParallel(300, 10, 10)
|
||||
->withPreparedSets(
|
||||
codeQuality: false,
|
||||
codingStyle: true,
|
||||
privatization: true,
|
||||
naming: false,
|
||||
earlyReturn: true,
|
||||
)
|
||||
->withDeadCodeLevel(40)
|
||||
->withMemoryLimit('3G')
|
||||
->withPhpSets(php83: true)
|
||||
->withTypeCoverageLevel(37);
|
||||
@ -2,32 +2,33 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use PHPNative\Support\Reflection\ClassReflector;
|
||||
use PHPNative\Support\Reflection\MethodReflector;
|
||||
use ReflectionClass;
|
||||
|
||||
interface Container
|
||||
{
|
||||
public function register(string $className, callable $definition): self;
|
||||
|
||||
public function singleton(string $className, callable $definition): self;
|
||||
public function singleton(string $className, object|callable $definition, ?string $tag = null): self;
|
||||
|
||||
public function config(object $config): self;
|
||||
|
||||
/**
|
||||
* @template TClassName
|
||||
* @template TClassName of object
|
||||
* @param class-string<TClassName> $className
|
||||
* @return TClassName
|
||||
*/
|
||||
public function get(string $className, mixed ...$params): object;
|
||||
public function get(string $className, ?string $tag = null, mixed ...$params): object;
|
||||
|
||||
public function call(object $object, string $methodName, mixed ...$params): mixed;
|
||||
public function invoke(MethodReflector $method, mixed ...$params): mixed;
|
||||
|
||||
/**
|
||||
* @template T of \PHPNative\Container\src\Initializer
|
||||
* @template U of \PHPNative\Container\src\DynamicInitializer
|
||||
* @param ReflectionClass|class-string<T>|class-string<U> $initializerClass
|
||||
* @return self
|
||||
* @template T of \PHPNative\Container\Initializer
|
||||
* @template U of \PHPNative\Container\DynamicInitializer
|
||||
* @param ClassReflector<T>|class-string<T>|class-string<U> $initializerClass
|
||||
*/
|
||||
public function addInitializer(ReflectionClass|string $initializerClass): self;
|
||||
public function addInitializer(ClassReflector|string $initializerClass): self;
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
|
||||
interface ContainerLog
|
||||
{
|
||||
/**
|
||||
* @return \PHPNative\Container\src\Context[]
|
||||
*/
|
||||
public function getStack(): array;
|
||||
|
||||
public function startResolving(): self;
|
||||
|
||||
public function addContext(Context $context): self;
|
||||
|
||||
public function addDependency(Dependency $dependency): self;
|
||||
|
||||
public function currentContext(): Context;
|
||||
|
||||
public function currentDependency(): ?Dependency;
|
||||
|
||||
public function getOrigin(): string;
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
final class Context
|
||||
{
|
||||
public function __construct(
|
||||
private ReflectionClass|ReflectionMethod|ReflectionFunction $reflector,
|
||||
/** @var \PHPNative\Container\src\Dependency[] $dependencies */
|
||||
private array $dependencies = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function addDependency(Dependency $dependency): self
|
||||
{
|
||||
$this->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,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,86 +2,87 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use Closure;
|
||||
use PHPNative\Support\Reflection\ClassReflector;
|
||||
use PHPNative\Support\Reflection\FunctionReflector;
|
||||
use PHPNative\Support\Reflection\MethodReflector;
|
||||
use PHPNative\Support\Reflection\ParameterReflector;
|
||||
use PHPNative\Support\Reflection\Reflector;
|
||||
use PHPNative\Support\Reflection\TypeReflector;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionIntersectionType;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionType;
|
||||
use ReflectionUnionType;
|
||||
|
||||
final readonly class Dependency
|
||||
{
|
||||
public function __construct(
|
||||
public ReflectionParameter|ReflectionClass $reflector,
|
||||
public Reflector|Closure|string $dependency,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->typeToString($this->getType());
|
||||
return $this->resolveName($this->dependency);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
public function getShortName(): string
|
||||
{
|
||||
$typeToString = $this->typeToString($this->getType());
|
||||
$parts = explode('\\', $typeToString);
|
||||
$typeToString = $parts[array_key_last($parts)];
|
||||
|
||||
return implode(
|
||||
' ',
|
||||
array_filter([
|
||||
$typeToString,
|
||||
'$' . $this->reflector->getName(),
|
||||
]),
|
||||
);
|
||||
return $this->resolveShortName($this->dependency);
|
||||
}
|
||||
|
||||
private function getType(): string|ReflectionType
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return match($this->reflector::class) {
|
||||
ReflectionParameter::class => $this->reflector->getType(),
|
||||
ReflectionClass::class => $this->reflector->getName(),
|
||||
return $this->getName() === $other->getName();
|
||||
}
|
||||
|
||||
public function getTypeName(): string
|
||||
{
|
||||
$dependency = $this->dependency;
|
||||
|
||||
if (is_string($dependency)) {
|
||||
$parts = explode('\\', $dependency);
|
||||
|
||||
return $parts[array_key_last($parts)];
|
||||
}
|
||||
|
||||
return match($dependency::class) {
|
||||
ClassReflector::class => $dependency->getType()->getShortName(),
|
||||
MethodReflector::class => $dependency->getDeclaringClass()->getType()->getShortName(),
|
||||
ParameterReflector::class => $dependency->getType()->getShortName(),
|
||||
TypeReflector::class => $dependency->getShortName(),
|
||||
default => 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
private function typeToString(string|ReflectionType|null $type): ?string
|
||||
private function resolveName(Reflector|Closure|string $dependency): string
|
||||
{
|
||||
if ($type === null) {
|
||||
return null;
|
||||
if (is_string($dependency)) {
|
||||
return $dependency;
|
||||
}
|
||||
|
||||
if (is_string($type)) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return match($type::class) {
|
||||
ReflectionIntersectionType::class => $this->intersectionTypeToString($type),
|
||||
ReflectionNamedType::class => $type->getName(),
|
||||
ReflectionUnionType::class => $this->unionTypeToString($type),
|
||||
return match($dependency::class) {
|
||||
FunctionReflector::class => $dependency->getName() . ' in ' . $dependency->getFileName() . ':' . $dependency->getStartLine(),
|
||||
ClassReflector::class => $dependency->getName(),
|
||||
MethodReflector::class => $dependency->getDeclaringClass()->getName() . '::' . $dependency->getName(),
|
||||
ParameterReflector::class => $dependency->getType()->getName(),
|
||||
TypeReflector::class => $dependency->getName(),
|
||||
default => 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
private function intersectionTypeToString(ReflectionIntersectionType $type): string
|
||||
private function resolveShortName(Reflector|Closure|string $dependency): string
|
||||
{
|
||||
return implode(
|
||||
'&',
|
||||
array_map(
|
||||
fn (ReflectionType $subType) => $this->typeToString($subType),
|
||||
$type->getTypes(),
|
||||
),
|
||||
);
|
||||
if (is_string($dependency)) {
|
||||
return $dependency;
|
||||
}
|
||||
|
||||
private function unionTypeToString(ReflectionUnionType $type): string
|
||||
{
|
||||
return implode(
|
||||
'|',
|
||||
array_map(
|
||||
fn (ReflectionType $subType) => $this->typeToString($subType),
|
||||
$type->getTypes(),
|
||||
),
|
||||
);
|
||||
return match($dependency::class) {
|
||||
FunctionReflector::class => $dependency->getShortName() . ' in ' . $dependency->getFileName() . ':' . $dependency->getStartLine(),
|
||||
ClassReflector::class => $dependency->getShortName(),
|
||||
MethodReflector::class => $dependency->getShortName(),
|
||||
ParameterReflector::class => $dependency->getType()->getShortName(),
|
||||
TypeReflector::class => $dependency->getShortName(),
|
||||
default => 'unknown',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
58
src/PHPNative/Container/src/DependencyChain.php
Normal file
58
src/PHPNative/Container/src/DependencyChain.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use Closure;
|
||||
use PHPNative\Container\Exceptions\CircularDependencyException;
|
||||
use PHPNative\Support\Reflection\Reflector;
|
||||
|
||||
final class DependencyChain
|
||||
{
|
||||
/**
|
||||
* @var \PHPNative\Container\Dependency[]
|
||||
*/
|
||||
private array $dependencies = [];
|
||||
|
||||
public function __construct(private string $origin)
|
||||
{
|
||||
}
|
||||
|
||||
public function add(Reflector|Closure|string $dependency): self
|
||||
{
|
||||
$dependency = new Dependency($dependency);
|
||||
|
||||
if (isset($this->dependencies[$dependency->getName()])) {
|
||||
throw new CircularDependencyException($this, $dependency);
|
||||
}
|
||||
|
||||
$this->dependencies[$dependency->getName()] = $dependency;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function first(): Dependency
|
||||
{
|
||||
return $this->dependencies[array_key_first($this->dependencies)];
|
||||
}
|
||||
|
||||
public function last(): Dependency
|
||||
{
|
||||
return $this->dependencies[array_key_last($this->dependencies)];
|
||||
}
|
||||
|
||||
/** @return \PHPNative\Container\Dependency[] */
|
||||
public function all(): array
|
||||
{
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
public function getOrigin(): string
|
||||
{
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
public function clone(): self
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
interface DynamicInitializer
|
||||
{
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use PHPNative\Container\Dependency;
|
||||
use PHPNative\Container\DependencyChain;
|
||||
|
||||
final class CannotAutowireException extends Exception
|
||||
{
|
||||
public function __construct(DependencyChain $chain, Dependency $brokenDependency)
|
||||
{
|
||||
$stack = $chain->all();
|
||||
|
||||
$firstDependency = $chain->first();
|
||||
|
||||
$message = PHP_EOL . PHP_EOL . "Cannot autowire {$firstDependency->getName()} because {$brokenDependency->getName()} cannot be resolved" . PHP_EOL;
|
||||
|
||||
$i = 0;
|
||||
|
||||
foreach ($stack as $currentDependency) {
|
||||
$pipe = match ($i) {
|
||||
0 => '┌──',
|
||||
count($stack) - 1 => '└──',
|
||||
default => '├──',
|
||||
};
|
||||
|
||||
$message .= PHP_EOL . "\t{$pipe} " . $currentDependency->getShortName();
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
$selectionLine = preg_replace_callback(
|
||||
pattern: '/(?<prefix>(.*))(?<selection>'. $brokenDependency->getTypeName() .'\s\$\w+)(.*)/',
|
||||
callback: function ($matches) {
|
||||
return str_repeat(' ', strlen($matches['prefix']) + 4)
|
||||
. str_repeat('▒', strlen($matches['selection']));
|
||||
},
|
||||
subject: $chain->last()->getShortName(),
|
||||
);
|
||||
|
||||
$message .= PHP_EOL;
|
||||
$message .= "\t{$selectionLine}";
|
||||
$message .= PHP_EOL;
|
||||
$message .= "Originally called in {$chain->getOrigin()}";
|
||||
$message .= PHP_EOL;
|
||||
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
@ -2,19 +2,20 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src\Exceptions;
|
||||
namespace PHPNative\Container\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use PHPNative\Container\src\ContainerLog;
|
||||
use PHPNative\Container\DependencyChain;
|
||||
use PHPNative\Support\Reflection\ClassReflector;
|
||||
use ReflectionClass;
|
||||
|
||||
final class CannotInstantiateDependencyException extends Exception
|
||||
{
|
||||
public function __construct(ReflectionClass $class, ContainerLog $containerLog)
|
||||
public function __construct(ClassReflector $class, DependencyChain $chain)
|
||||
{
|
||||
$message = "Cannot resolve {$class->getName()} because it is not an instantiable class. Maybe it's missing an initializer class?" . PHP_EOL;
|
||||
|
||||
$stack = $containerLog->getStack();
|
||||
$stack = $chain->all();
|
||||
|
||||
if ($stack === []) {
|
||||
parent::__construct($message);
|
||||
@ -22,11 +23,9 @@ final class CannotInstantiateDependencyException extends Exception
|
||||
return;
|
||||
}
|
||||
|
||||
$lastContext = $stack[array_key_last($stack)];
|
||||
|
||||
$i = 0;
|
||||
|
||||
foreach ($stack as $currentContext) {
|
||||
foreach ($stack as $currentDependency) {
|
||||
$pipe = match (true) {
|
||||
count($stack) > 1 && $i === 0 => '┌──',
|
||||
count($stack) > 1 && $i === count($stack) - 1 => '└──',
|
||||
@ -34,23 +33,24 @@ final class CannotInstantiateDependencyException extends Exception
|
||||
default => '├──',
|
||||
};
|
||||
|
||||
$message .= PHP_EOL . "\t{$pipe} " . $currentContext;
|
||||
$message .= PHP_EOL . "\t{$pipe} " . $currentDependency->getShortName();
|
||||
|
||||
$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}";
|
||||
$lastDependency = $chain->last();
|
||||
// $currentDependencyName = $lastDependency->getShortName();
|
||||
// $firstPart = explode($currentDependencyName, (string)$lastDependency)[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 .= PHP_EOL . PHP_EOL;
|
||||
|
||||
$message .= "Originally called in {$containerLog->getOrigin()}";
|
||||
$message .= "Originally called in {$chain->getOrigin()}";
|
||||
$message .= PHP_EOL;
|
||||
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,51 +2,62 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use ArrayIterator;
|
||||
use Closure;
|
||||
use PHPNative\Container\Exceptions\CannotAutowireException;
|
||||
use PHPNative\Container\src\Exceptions\CannotInstantiateDependencyException;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionIntersectionType;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionUnionType;
|
||||
use PHPNative\Container\Exceptions\CannotInstantiateDependencyException;
|
||||
use PHPNative\Container\Exceptions\CannotResolveTaggedDependency;
|
||||
use PHPNative\Support\Reflection\ClassReflector;
|
||||
use PHPNative\Support\Reflection\FunctionReflector;
|
||||
use PHPNative\Support\Reflection\MethodReflector;
|
||||
use PHPNative\Support\Reflection\ParameterReflector;
|
||||
use PHPNative\Support\Reflection\TypeReflector;
|
||||
use Throwable;
|
||||
use function PHPNative\attribute;
|
||||
|
||||
final class GenericContainer implements Container
|
||||
{
|
||||
use HasInstance;
|
||||
|
||||
public function __construct(
|
||||
private array $definitions = [],
|
||||
private array $singletons = [],
|
||||
/** @var ArrayIterator<array-key, mixed> $definitions */
|
||||
private ArrayIterator $definitions = new ArrayIterator(),
|
||||
|
||||
/**
|
||||
* @template T of \PHPNative\Container\src\Initializer
|
||||
* @var class-string<T> $initializers
|
||||
*/
|
||||
private array $initializers = [],
|
||||
/** @var ArrayIterator<array-key, mixed> $singletons */
|
||||
private ArrayIterator $singletons = new ArrayIterator(),
|
||||
|
||||
/**
|
||||
* @template T of \PHPNative\Container\src\DynamicInitializer
|
||||
* @var class-string<T> $dynamicInitializers
|
||||
*/
|
||||
private array $dynamicInitializers = [],
|
||||
private readonly ContainerLog $log = new InMemoryContainerLog(),
|
||||
/** @var ArrayIterator<array-key, class-string> $initializers */
|
||||
private ArrayIterator $initializers = new ArrayIterator(),
|
||||
|
||||
/** @var ArrayIterator<array-key, class-string> $dynamicInitializers */
|
||||
private ArrayIterator $dynamicInitializers = new ArrayIterator(),
|
||||
private ?DependencyChain $chain = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function setInitializers(array $initializers): void
|
||||
public function setInitializers(array $initializers): self
|
||||
{
|
||||
$this->initializers = $initializers;
|
||||
$this->initializers = new ArrayIterator($initializers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDynamicInitializers(array $dynamicInitializers): self
|
||||
{
|
||||
$this->dynamicInitializers = new ArrayIterator($dynamicInitializers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInitializers(): array
|
||||
{
|
||||
return $this->initializers;
|
||||
return $this->initializers->getArrayCopy();
|
||||
}
|
||||
|
||||
public function getDynamicInitializers(): array
|
||||
{
|
||||
return $this->dynamicInitializers->getArrayCopy();
|
||||
}
|
||||
|
||||
public function register(string $className, callable $definition): self
|
||||
@ -56,136 +67,154 @@ final class GenericContainer implements Container
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function singleton(string $className, callable $definition): self
|
||||
public function singleton(string $className, object|callable $definition, ?string $tag = null): self
|
||||
{
|
||||
$this->definitions[$className] = function () use ($definition, $className) {
|
||||
$instance = $definition($this);
|
||||
$className = $this->resolveTaggedName($className, $tag);
|
||||
|
||||
$this->singletons[$className] = $instance;
|
||||
|
||||
return $instance;
|
||||
};
|
||||
$this->singletons[$className] = $definition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function config(object $config): self
|
||||
{
|
||||
$this->singleton($config::class, fn () => $config);
|
||||
$this->singleton($config::class, $config);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get(string $className, mixed ...$params): object
|
||||
public function get(string $className, ?string $tag = null, mixed ...$params): object
|
||||
{
|
||||
$this->log->startResolving();
|
||||
$this->resolveChain();
|
||||
|
||||
return $this->resolve($className, ...$params);
|
||||
$dependency = $this->resolve(
|
||||
className: $className,
|
||||
tag: $tag,
|
||||
params: $params,
|
||||
);
|
||||
|
||||
$this->stopChain();
|
||||
|
||||
return $dependency;
|
||||
}
|
||||
|
||||
public function call(string|object $object, string $methodName, ...$params): mixed
|
||||
public function invoke(MethodReflector $method, mixed ...$params): mixed
|
||||
{
|
||||
$this->log->startResolving();
|
||||
$this->resolveChain();
|
||||
|
||||
$object = is_string($object) ? $this->get($object) : $object;
|
||||
$object = $this->get($method->getDeclaringClass()->getName());
|
||||
|
||||
$reflectionMethod = (new ReflectionClass($object))->getMethod($methodName);
|
||||
$parameters = $this->autowireDependencies($method, $params);
|
||||
|
||||
$parameters = $this->autowireDependencies($reflectionMethod, $params);
|
||||
$this->stopChain();
|
||||
|
||||
return $reflectionMethod->invokeArgs($object, $parameters);
|
||||
return $method->invokeArgs($object, $parameters);
|
||||
}
|
||||
|
||||
public function addInitializer(ReflectionClass|string $initializerClass): Container
|
||||
public function addInitializer(ClassReflector|string $initializerClass): Container
|
||||
{
|
||||
$initializerClass = $initializerClass instanceof ReflectionClass
|
||||
? $initializerClass
|
||||
: new ReflectionClass($initializerClass);
|
||||
if (! $initializerClass instanceof ClassReflector) {
|
||||
$initializerClass = new ClassReflector($initializerClass);
|
||||
}
|
||||
|
||||
// First, we check whether this is a DynamicInitializer,
|
||||
// which don't have a one-to-one mapping
|
||||
if ($initializerClass->implementsInterface(DynamicInitializer::class)) {
|
||||
if ($initializerClass->getType()->matches(DynamicInitializer::class)) {
|
||||
$this->dynamicInitializers[] = $initializerClass->getName();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$initializeMethod = $initializerClass->getMethod('initialize');
|
||||
|
||||
// We resolve the optional Tag attribute from this initializer class
|
||||
$singleton = $initializeMethod->getAttribute(Singleton::class);
|
||||
|
||||
// For normal Initializers, we'll use the return type
|
||||
// to determine which dependency they resolve
|
||||
$returnTypes = $initializerClass->getMethod('initialize')->getReturnType();
|
||||
$returnType = $initializeMethod->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();
|
||||
foreach ($returnType->split() as $type) {
|
||||
$this->initializers[$this->resolveTaggedName($type->getName(), $singleton?->tag)] = $initializerClass->getName();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function resolve(string $className, mixed ...$params): object
|
||||
private function resolve(string $className, ?string $tag = null, mixed ...$params): object
|
||||
{
|
||||
$class = new ClassReflector($className);
|
||||
|
||||
$dependencyName = $this->resolveTaggedName($className, $tag);
|
||||
|
||||
// Check if the class has been registered as a singleton.
|
||||
if ($instance = $this->singletons[$className] ?? null) {
|
||||
$this->log->addContext(new Context(new ReflectionClass($className)));
|
||||
if ($instance = $this->singletons[$dependencyName] ?? null) {
|
||||
if ($instance instanceof Closure) {
|
||||
$instance = $instance($this);
|
||||
$this->singletons[$className] = $instance;
|
||||
}
|
||||
|
||||
$this->resolveChain()->add($class);
|
||||
|
||||
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)));
|
||||
if ($definition = $this->definitions[$dependencyName] ?? null) {
|
||||
$this->resolveChain()->add(new FunctionReflector($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);
|
||||
if (($initializer = $this->initializerFor($class, $tag)) !== null) {
|
||||
$initializerClass = new ClassReflector($initializer);
|
||||
|
||||
return $this->get($className);
|
||||
$this->resolveChain()->add($initializerClass);
|
||||
|
||||
$object = match (true) {
|
||||
$initializer instanceof Initializer => $initializer->initialize($this->clone()),
|
||||
$initializer instanceof DynamicInitializer => $initializer->initialize($class, $this->clone()),
|
||||
};
|
||||
|
||||
$singleton = $initializerClass->getAttribute(Singleton::class)
|
||||
?? $initializerClass->getMethod('initialize')->getAttribute(Singleton::class);
|
||||
|
||||
if ($singleton !== null) {
|
||||
$this->singleton($className, $object, $tag);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
// If we're requesting a tagged dependency and haven't resolved it at this point, something's wrong
|
||||
if ($tag) {
|
||||
throw new CannotResolveTaggedDependency($this->chain, new Dependency($className), $tag);
|
||||
}
|
||||
|
||||
// Finally, autowire the class.
|
||||
return $this->autowire($className, ...$params);
|
||||
}
|
||||
|
||||
private function initializerFor(string $className): null|Initializer|DynamicInitializer
|
||||
private function initializerFor(ClassReflector $class, ?string $tag = null): 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)
|
||||
) {
|
||||
if ($class->getType()->matches(Initializer::class) || $class->getType()->matches(DynamicInitializer::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($initializerClass = $this->initializers[$className] ?? null) {
|
||||
if ($initializerClass = $this->initializers[$this->resolveTaggedName($class, $tag)] ?? 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) {
|
||||
/** @var DynamicInitializer $initializer */
|
||||
$initializer = $this->resolve($initializerClass);
|
||||
|
||||
if (! $initializer->canInitialize($className)) {
|
||||
if (! $initializer->canInitialize($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -197,40 +226,51 @@ final class GenericContainer implements Container
|
||||
|
||||
private function autowire(string $className, mixed ...$params): object
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
$classReflector = new ClassReflector($className);
|
||||
|
||||
$constructor = $reflectionClass->getConstructor();
|
||||
$constructor = $classReflector->getConstructor();
|
||||
|
||||
if (! $reflectionClass->isInstantiable()) {
|
||||
throw new CannotInstantiateDependencyException($reflectionClass, $this->log);
|
||||
if (! $classReflector->isInstantiable()) {
|
||||
throw new CannotInstantiateDependencyException($classReflector, $this->chain);
|
||||
}
|
||||
|
||||
return $constructor === null
|
||||
$instance = $constructor === null
|
||||
// If there isn't a constructor, don't waste time
|
||||
// trying to build it.
|
||||
? $reflectionClass->newInstanceWithoutConstructor()
|
||||
? $classReflector->newInstanceWithoutConstructor()
|
||||
|
||||
// Otherwise, use our autowireDependencies helper to automagically
|
||||
// build up each parameter.
|
||||
: $reflectionClass->newInstanceArgs(
|
||||
: $classReflector->newInstanceArgs(
|
||||
$this->autowireDependencies($constructor, $params),
|
||||
);
|
||||
|
||||
if (
|
||||
! $classReflector->getType()->matches(Initializer::class)
|
||||
&& ! $classReflector->getType()->matches(DynamicInitializer::class)
|
||||
&& $classReflector->hasAttribute(Singleton::class)
|
||||
) {
|
||||
$this->singleton($className, $instance);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReflectionParameter[]
|
||||
* @return ParameterReflector[]
|
||||
*/
|
||||
private function autowireDependencies(ReflectionMethod $method, array $parameters = []): array
|
||||
private function autowireDependencies(MethodReflector $method, array $parameters = []): array
|
||||
{
|
||||
$this->log->addContext(new Context($method));
|
||||
$this->resolveChain()->add($method);
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
// Build the class by iterating through its
|
||||
// dependencies and resolving them.
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
$dependencies[] = $this->autowireDependency(
|
||||
$dependencies[] = $this->clone()->autowireDependency(
|
||||
parameter: $parameter,
|
||||
tag: $parameter->getAttribute(Tag::class)?->name,
|
||||
providedValue: $parameters[$parameter->getName()] ?? null,
|
||||
);
|
||||
}
|
||||
@ -238,29 +278,24 @@ final class GenericContainer implements Container
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function autowireDependency(ReflectionParameter $parameter, mixed $providedValue = null): mixed
|
||||
private function autowireDependency(ParameterReflector $parameter, ?string $tag, 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()) {
|
||||
if ($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) {
|
||||
foreach ($parameter->getType()->split() as $type) {
|
||||
try {
|
||||
return $this->autowireObjectDependency($type, $providedValue);
|
||||
return $this->autowireObjectDependency(
|
||||
type: $type,
|
||||
tag: $tag,
|
||||
providedValue: $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
|
||||
@ -271,35 +306,29 @@ final class GenericContainer implements Container
|
||||
|
||||
// If the dependency has a default value, we do our best to prevent
|
||||
// an error by using that.
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
if ($parameter->hasDefaultValue()) {
|
||||
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);
|
||||
throw $lastThrowable ?? new CannotAutowireException($this->chain, new Dependency($parameter));
|
||||
}
|
||||
|
||||
private function autowireObjectDependency(ReflectionNamedType $type, mixed $providedValue): mixed
|
||||
private function autowireObjectDependency(TypeReflector $type, ?string $tag, 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())) {
|
||||
if ($type->accepts($providedValue)) {
|
||||
return $providedValue;
|
||||
}
|
||||
|
||||
// If we can successfully retrieve an instance
|
||||
// of the necessary dependency, return it.
|
||||
if ($instance = $this->resolve($type->getName())) {
|
||||
return $instance;
|
||||
return $this->resolve(className: $type->getName(), tag: $tag);
|
||||
}
|
||||
|
||||
// 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
|
||||
private function autowireBuiltinDependency(ParameterReflector $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
|
||||
@ -310,27 +339,59 @@ final class GenericContainer implements Container
|
||||
|
||||
// If the dependency has a default value, we might as well
|
||||
// use that at this point.
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
if ($parameter->hasDefaultValue()) {
|
||||
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()
|
||||
) {
|
||||
if ($parameter->isVariadic() || $parameter->isIterable()) {
|
||||
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()) {
|
||||
if (! $parameter->isRequired()) {
|
||||
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);
|
||||
throw new CannotAutowireException($this->chain, new Dependency($parameter));
|
||||
}
|
||||
|
||||
private function clone(): self
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
private function resolveChain(): DependencyChain
|
||||
{
|
||||
if ($this->chain === null) {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
$this->chain = new DependencyChain($trace[1]['file'] . ':' . $trace[1]['line']);
|
||||
}
|
||||
|
||||
return $this->chain;
|
||||
}
|
||||
|
||||
private function stopChain(): void
|
||||
{
|
||||
$this->chain = null;
|
||||
}
|
||||
|
||||
public function __clone(): void
|
||||
{
|
||||
$this->chain = $this->chain?->clone();
|
||||
}
|
||||
|
||||
private function resolveTaggedName(string|ClassReflector $class, ?string $tag): string
|
||||
{
|
||||
$className = is_string($class) ? $class : $class->getName();
|
||||
|
||||
return $tag
|
||||
? "{$className}#{$tag}"
|
||||
: $className;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
trait HasInstance
|
||||
{
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
|
||||
use Exception;
|
||||
use Tempest\Container\Exceptions\CircularDependencyException;
|
||||
|
||||
final class InMemoryContainerLog implements ContainerLog
|
||||
{
|
||||
private string $origin = '';
|
||||
|
||||
public function __construct(
|
||||
/** @var \PHPNative\Container\src\Context[] $stack */
|
||||
private array $stack = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function startResolving(): ContainerLog
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
interface Initializer
|
||||
{
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Container\src;
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use Attribute;
|
||||
|
||||
|
||||
13
src/PHPNative/Container/src/Tag.php
Normal file
13
src/PHPNative/Container/src/Tag.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Container;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute]
|
||||
final readonly class Tag
|
||||
{
|
||||
public function __construct(public string $name)
|
||||
{
|
||||
}
|
||||
}
|
||||
112
src/PHPNative/Core/src/Collection.php
Normal file
112
src/PHPNative/Core/src/Collection.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Core;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
abstract class Collection implements IteratorAggregate
|
||||
{
|
||||
public function __construct(private array $elements)
|
||||
{
|
||||
}
|
||||
|
||||
public static function createEmpty(): static
|
||||
{
|
||||
return new static([]);
|
||||
}
|
||||
|
||||
public static function fromMap(array $items, callable $fn): static
|
||||
{
|
||||
return new static(array_map($fn, $items));
|
||||
}
|
||||
|
||||
public function reduce(callable $fn, mixed $initial): mixed
|
||||
{
|
||||
return array_reduce($this->elements, $fn, $initial);
|
||||
}
|
||||
|
||||
public function map(callable $fn): array
|
||||
{
|
||||
return array_map($fn, $this->elements);
|
||||
}
|
||||
|
||||
public function each(callable $fn): void
|
||||
{
|
||||
array_walk($this->elements, $fn);
|
||||
}
|
||||
|
||||
public function some(callable $fn): bool
|
||||
{
|
||||
foreach ($this->elements as $index => $element) {
|
||||
if ($fn($element, $index, $this->elements)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function filter(callable $fn): static
|
||||
{
|
||||
return new static(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH));
|
||||
}
|
||||
|
||||
public function first(): mixed
|
||||
{
|
||||
return reset($this->elements);
|
||||
}
|
||||
|
||||
public function last(): mixed
|
||||
{
|
||||
return end($this->elements);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->elements);
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->elements);
|
||||
}
|
||||
|
||||
public function add(mixed $element): void
|
||||
{
|
||||
$this->elements[] = $element;
|
||||
}
|
||||
|
||||
public function values(): array
|
||||
{
|
||||
return array_values($this->elements);
|
||||
}
|
||||
|
||||
public function items(): array
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->elements);
|
||||
}
|
||||
|
||||
public function removeFirstItem(): void
|
||||
{
|
||||
if(count($this->elements) > 0) {
|
||||
array_shift($this->elements);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeItem($item): void
|
||||
{
|
||||
$temp = $this->filter(function ($value, $key) use($item){
|
||||
return $value::class!=$item::class;
|
||||
});
|
||||
|
||||
$this->elements = $temp->items();
|
||||
}
|
||||
}
|
||||
17
src/PHPNative/Core/src/PathHelper.php
Normal file
17
src/PHPNative/Core/src/PathHelper.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Core;
|
||||
|
||||
final readonly class PathHelper
|
||||
{
|
||||
public static function make(string ...$parts): string
|
||||
{
|
||||
$path = implode('/', $parts);
|
||||
|
||||
return str_replace(
|
||||
['//', '\\'],
|
||||
DIRECTORY_SEPARATOR,
|
||||
$path,
|
||||
);
|
||||
}
|
||||
}
|
||||
19
src/PHPNative/Core/src/TypedCollection.php
Normal file
19
src/PHPNative/Core/src/TypedCollection.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Core;
|
||||
|
||||
abstract class TypedCollection extends Collection
|
||||
{
|
||||
public function __construct(array $elements = [])
|
||||
{
|
||||
parent::__construct($elements);
|
||||
}
|
||||
|
||||
abstract protected function type(): string;
|
||||
|
||||
public function add(mixed $element): void
|
||||
{
|
||||
parent::add($element);
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Core;
|
||||
|
||||
class Window
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
17
src/PHPNative/Event/composer.json
Normal file
17
src/PHPNative/Event/composer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "phpnative/event",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Event\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Event\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/PHPNative/Event/src/Driver.php
Normal file
23
src/PHPNative/Event/src/Driver.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
class Driver
|
||||
{
|
||||
public function __construct(private \SDL_Event $event = new \SDL_Event())
|
||||
{
|
||||
}
|
||||
|
||||
public function pollEvent(): Event
|
||||
{
|
||||
\SDL_PollEvent($this->event);
|
||||
return match($this->event->type) {
|
||||
\SDL_EVENT_QUIT => new \PHPNative\Event\SystemEvent(EventType::QUIT),
|
||||
\SDL_EVENT_MOUSE_BUTTON_DOWN => new MouseDown(EventType::MOUSEBUTTON_DOWN, $this->event->button->x, $this->event->button->y ),
|
||||
\SDL_EVENT_MOUSE_BUTTON_UP => new MouseUp(EventType::MOUSEBUTTON_UP, $this->event->button->x, $this->event->button->y ),
|
||||
\SDL_EVENT_MOUSE_MOTION => new MouseMove(EventType::MOUSEMOVE, $this->event->motion->x, $this->event->motion->y ),
|
||||
default => new \PHPNative\Event\SystemEvent(EventType::NOOP)
|
||||
};
|
||||
}
|
||||
}
|
||||
8
src/PHPNative/Event/src/Event.php
Normal file
8
src/PHPNative/Event/src/Event.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
interface Event
|
||||
{
|
||||
public function getType(): EventType;
|
||||
}
|
||||
14
src/PHPNative/Event/src/EventCollection.php
Normal file
14
src/PHPNative/Event/src/EventCollection.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
use PHPNative\Core\TypedCollection;
|
||||
|
||||
final class EventCollection extends TypedCollection
|
||||
{
|
||||
protected function type(): string
|
||||
{
|
||||
return Event::class;
|
||||
}
|
||||
}
|
||||
23
src/PHPNative/Event/src/EventType.php
Normal file
23
src/PHPNative/Event/src/EventType.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
enum EventType: int
|
||||
{
|
||||
case NOOP = 0;
|
||||
case QUIT = 1;
|
||||
case CLOSE_WINDOW = 2;
|
||||
case WINDOW_FOCUS_LOST = 1000;
|
||||
case WINDOW_FOCUS_GAINED = 1001;
|
||||
case WINDOW_RESIZED = 1002;
|
||||
case WINDOW_CLOSE = 1003;
|
||||
|
||||
case MOUSEBUTTON_DOWN = 2000;
|
||||
case MOUSEBUTTON_UP = 2001;
|
||||
case MOUSEMOVE = 2002;
|
||||
|
||||
case TEXTINPUT = 3000;
|
||||
case KEYUP = 3001;
|
||||
case KEYDOWN = 3002;
|
||||
|
||||
}
|
||||
17
src/PHPNative/Event/src/MouseDown.php
Normal file
17
src/PHPNative/Event/src/MouseDown.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
class MouseDown implements Event
|
||||
{
|
||||
public function __construct(public EventType $type = EventType::MOUSEBUTTON_DOWN, public int $x = 0, public int $y = 0)
|
||||
{
|
||||
}
|
||||
|
||||
public function getType(): EventType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/PHPNative/Event/src/MouseMove.php
Normal file
17
src/PHPNative/Event/src/MouseMove.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
class MouseMove implements Event
|
||||
{
|
||||
public function __construct(public EventType $type = EventType::MOUSEMOVE, public int $x = 0, public int $y = 0)
|
||||
{
|
||||
}
|
||||
|
||||
public function getType(): EventType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/PHPNative/Event/src/MouseUp.php
Normal file
17
src/PHPNative/Event/src/MouseUp.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
class MouseUp implements Event
|
||||
{
|
||||
public function __construct(public EventType $type = EventType::MOUSEBUTTON_UP, public int $x = 0, public int $y = 0)
|
||||
{
|
||||
}
|
||||
|
||||
public function getType(): EventType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
}
|
||||
16
src/PHPNative/Event/src/SystemEvent.php
Normal file
16
src/PHPNative/Event/src/SystemEvent.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Event;
|
||||
|
||||
class SystemEvent implements Event
|
||||
{
|
||||
public function __construct(public EventType $type = EventType::NOOP)
|
||||
{
|
||||
}
|
||||
|
||||
public function getType(): EventType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
"psr-4": {
|
||||
"PHPNative\\Framework\\": "src"
|
||||
}
|
||||
},
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Framework\\Tests\\": "tests"
|
||||
|
||||
10
src/PHPNative/Framework/src/App.php
Normal file
10
src/PHPNative/Framework/src/App.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework;
|
||||
|
||||
interface App
|
||||
{
|
||||
public function getName(): string;
|
||||
|
||||
public function getStartWindow(): string;
|
||||
}
|
||||
10
src/PHPNative/Framework/src/Application/Application.php
Normal file
10
src/PHPNative/Framework/src/Application/Application.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Application;
|
||||
|
||||
interface Application
|
||||
{
|
||||
|
||||
public function run(string|null $start): void;
|
||||
|
||||
}
|
||||
8
src/PHPNative/Framework/src/Application/Console.php
Normal file
8
src/PHPNative/Framework/src/Application/Console.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Application;
|
||||
|
||||
class Console
|
||||
{
|
||||
|
||||
}
|
||||
52
src/PHPNative/Framework/src/Application/Gui.php
Normal file
52
src/PHPNative/Framework/src/Application/Gui.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Application;
|
||||
|
||||
use ArgumentCountError;
|
||||
use PHPNative\Container\Container;
|
||||
use PHPNative\Framework\App;
|
||||
use PHPNative\Framework\Lifecycle\Lifecycle;
|
||||
use PHPNative\Framework\PHPNative;
|
||||
use Throwable;
|
||||
|
||||
final readonly class Gui implements Application
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private Container $container
|
||||
) {
|
||||
}
|
||||
|
||||
public static function boot(
|
||||
string $name = 'PHPNative',
|
||||
?string $root = null,
|
||||
array $discoveryLocations = [],
|
||||
): self {
|
||||
$container = PHPNative::boot($root);
|
||||
|
||||
$application = $container->get(Gui::class);
|
||||
|
||||
return $application;
|
||||
}
|
||||
|
||||
|
||||
public function run(string|null $start): void
|
||||
{
|
||||
try {
|
||||
$lifeCycle = $this->container->get(Lifecycle::class);
|
||||
|
||||
try {
|
||||
$lifeCycle->show($this->container->get($start));
|
||||
$lifeCycle->run();
|
||||
} catch (ArgumentCountError $e) {
|
||||
var_dump($e->getMessage());
|
||||
var_dump($e->getFile());
|
||||
var_dump($e->getTraceAsString());
|
||||
}
|
||||
} catch (Throwable $throwable) {
|
||||
var_dump($throwable->getMessage());
|
||||
var_dump($throwable->getFile());
|
||||
var_dump($throwable->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +1,55 @@
|
||||
<?PHP
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\src\Application;
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPNative\Container\src\Container;
|
||||
use PHPNative\Container\src\GenericContainer;
|
||||
use PHPNative\Framework\Application\ConfigBootstrap;
|
||||
use PHPNative\Framework\Application\DiscoveryBootstrap;
|
||||
use PHPNative\Framework\Application\DiscoveryLocationBootstrap;
|
||||
namespace PHPNative\Framework\Application;
|
||||
|
||||
use PHPNative\Container\Container;
|
||||
use PHPNative\Container\GenericContainer;
|
||||
use PHPNative\Framework\Discovery\DiscoveryLocationBootstrap;
|
||||
use PHPNative\Framework\Discovery\LoadDiscoveryClasses;
|
||||
use PHPNative\Framework\Discovery\LoadDiscoveryLocations;
|
||||
|
||||
final class Kernel
|
||||
{
|
||||
public readonly Container $container;
|
||||
|
||||
private $loop;
|
||||
|
||||
public function __construct(
|
||||
public string $root
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$container = $this->createContainer();
|
||||
|
||||
$bootstraps = [
|
||||
DiscoveryLocationBootstrap::class,
|
||||
ConfigBootstrap::class,
|
||||
DiscoveryBootstrap::class,
|
||||
public array $discoveryClasses = [
|
||||
];
|
||||
|
||||
foreach ($bootstraps as $bootstrap) {
|
||||
$container->get(
|
||||
$bootstrap,
|
||||
kernel: $this,
|
||||
)->boot();
|
||||
public function __construct(
|
||||
public string $root,
|
||||
public array $discoveryLocations = [],
|
||||
?Container $container = null,
|
||||
) {
|
||||
$this->container = $container ?? $this->createContainer();
|
||||
|
||||
$this
|
||||
->registerKernel()
|
||||
->loadDiscoveryLocations()
|
||||
->loadDiscovery();
|
||||
|
||||
}
|
||||
|
||||
return $container;
|
||||
private function registerKernel(): self
|
||||
{
|
||||
$this->container->singleton(self::class, $this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function loadDiscoveryLocations(): self
|
||||
{
|
||||
($this->container->get(LoadDiscoveryLocations::class))();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function loadDiscovery(): self
|
||||
{
|
||||
($this->container->get(LoadDiscoveryClasses::class))();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function createContainer(): Container
|
||||
@ -50,11 +58,8 @@ final class Kernel
|
||||
|
||||
GenericContainer::setInstance($container);
|
||||
|
||||
$container
|
||||
->singleton(self::class, fn () => $this)
|
||||
->singleton(Container::class, fn () => $container)
|
||||
;
|
||||
$container->singleton(Container::class, fn () => $container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\src\Application;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\Application;
|
||||
|
||||
use PHPNative\UI\View;
|
||||
|
||||
interface Window
|
||||
{
|
||||
public function getTitle(): string;
|
||||
public function getView(): View;
|
||||
}
|
||||
8
src/PHPNative/Framework/src/Discovery/Bootstrap.php
Normal file
8
src/PHPNative/Framework/src/Discovery/Bootstrap.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Discovery;
|
||||
|
||||
interface Bootstrap
|
||||
{
|
||||
public function boot(): void;
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\src\Discovery;
|
||||
namespace PHPNative\Framework\Discovery;
|
||||
|
||||
use PHPNative\Container\src\Container;
|
||||
use ReflectionClass;
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\src\Discovery;
|
||||
|
||||
use PHPNative\AppConfig;
|
||||
use PHPNative\Container\src\Container;
|
||||
use ReflectionClass;
|
||||
|
||||
final readonly class DiscoveryDiscovery implements Discovery
|
||||
{
|
||||
public const CACHE_PATH = __DIR__ . '/discovery-discovery.cache.php';
|
||||
|
||||
public function __construct(
|
||||
private AppConfig $appConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function discover(ReflectionClass $class): void
|
||||
{
|
||||
if (
|
||||
! $class->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);
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\src\Discovery;
|
||||
namespace PHPNative\Framework\Discovery;
|
||||
|
||||
final readonly class DiscoveryLocation
|
||||
{
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\src\Discovery;
|
||||
|
||||
use PHPNative\Container\src\Container;
|
||||
@ -8,7 +10,7 @@ use ReflectionClass;
|
||||
|
||||
final readonly class InitializerDiscovery implements Discovery
|
||||
{
|
||||
private const CACHE_PATH = __DIR__ . '/initializer-discovery.cache.php';
|
||||
private const string CACHE_PATH = __DIR__ . '/initializer-discovery.cache.php';
|
||||
|
||||
public function __construct(
|
||||
private Container $container,
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\Discovery;
|
||||
|
||||
use PHPNative\Container\Container;
|
||||
use PHPNative\Framework\Application\Kernel;
|
||||
|
||||
final readonly class LoadDiscoveryClasses
|
||||
{
|
||||
public function __construct(
|
||||
private Kernel $kernel,
|
||||
private Container $container,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
reset($this->kernel->discoveryClasses);
|
||||
|
||||
while ($discoveryClass = current($this->kernel->discoveryClasses)) {
|
||||
/** @var Discovery $discovery */
|
||||
$discovery = $this->container->get($discoveryClass);
|
||||
|
||||
if ($this->kernel->discoveryCache && $discovery->hasCache()) {
|
||||
$discovery->restoreCache($this->container);
|
||||
next($this->kernel->discoveryClasses);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->kernel->discoveryLocations as $discoveryLocation) {
|
||||
$directories = new RecursiveDirectoryIterator($discoveryLocation->path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
|
||||
$files = new RecursiveIteratorIterator($directories);
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($files as $file) {
|
||||
$fileName = $file->getFilename();
|
||||
|
||||
if ($fileName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fileName === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fileName === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$input = $file->getPathname();
|
||||
|
||||
if (ucfirst($fileName) === $fileName) {
|
||||
// Trim ending slashing from path
|
||||
$pathWithoutSlashes = rtrim($discoveryLocation->path, '\\/');
|
||||
|
||||
// Try to create a PSR-compliant class name from the path
|
||||
$className = str_replace(
|
||||
[
|
||||
$pathWithoutSlashes,
|
||||
'/',
|
||||
'\\\\',
|
||||
'.php',
|
||||
],
|
||||
[
|
||||
$discoveryLocation->namespace,
|
||||
'\\',
|
||||
'\\',
|
||||
'',
|
||||
],
|
||||
$file->getPathname(),
|
||||
);
|
||||
|
||||
try {
|
||||
$input = new ClassReflector($className);
|
||||
} catch (Throwable) {
|
||||
// Nothing should happen
|
||||
}
|
||||
}
|
||||
|
||||
if ($input instanceof ClassReflector) {
|
||||
$discovery->discover($input);
|
||||
} elseif ($discovery instanceof DiscoversPath) {
|
||||
$discovery->discoverPath($input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next($this->kernel->discoveryClasses);
|
||||
|
||||
$discovery->storeCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
122
src/PHPNative/Framework/src/Discovery/LoadDiscoveryLocations.php
Normal file
122
src/PHPNative/Framework/src/Discovery/LoadDiscoveryLocations.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Discovery;
|
||||
|
||||
use PHPNative\Core\PathHelper;
|
||||
use PHPNative\Framework\Application\Kernel;
|
||||
use PHPNative\Framework\Discovery\DiscoveryLocation;
|
||||
|
||||
final readonly class LoadDiscoveryLocations
|
||||
{
|
||||
public function __construct(
|
||||
private Kernel $kernel,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
$this->kernel->discoveryLocations =
|
||||
[
|
||||
...$this->kernel->discoveryLocations,
|
||||
...$this->discoverCorePackages(),
|
||||
...$this->discoverAppNamespaces(),
|
||||
...$this->discoverVendorPackages(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DiscoveryLocation[]
|
||||
*/
|
||||
private function discoverCorePackages(): array
|
||||
{
|
||||
$composerPath = PathHelper::make($this->kernel->root, 'vendor/composer');
|
||||
$installed = $this->loadJsonFile(PathHelper::make($composerPath, 'installed.json'));
|
||||
$packages = $installed['packages'] ?? [];
|
||||
|
||||
$discoveredLocations = [];
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$packageName = ($package['name'] ?? '');
|
||||
$isTempest = str_starts_with($packageName, 'tempest');
|
||||
|
||||
if (! $isTempest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$packagePath = PathHelper::make($composerPath, $package['install-path'] ?? '');
|
||||
|
||||
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
|
||||
$namespacePath = PathHelper::make($packagePath, $namespacePath);
|
||||
|
||||
$discoveredLocations[] = new DiscoveryLocation($namespace, $namespacePath);
|
||||
}
|
||||
}
|
||||
|
||||
return $discoveredLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DiscoveryLocation[]
|
||||
*/
|
||||
private function discoverAppNamespaces(): array
|
||||
{
|
||||
$composer = $this->loadJsonFile(PathHelper::make($this->kernel->root, 'composer.json'));
|
||||
$namespaceMap = $composer['autoload']['psr-4'] ?? [];
|
||||
|
||||
$discoveredLocations = [];
|
||||
|
||||
foreach ($namespaceMap as $namespace => $path) {
|
||||
$path = PathHelper::make($this->kernel->root, $path);
|
||||
|
||||
$discoveredLocations[] = new DiscoveryLocation($namespace, $path);
|
||||
}
|
||||
|
||||
return $discoveredLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DiscoveryLocation[]
|
||||
*/
|
||||
private function discoverVendorPackages(): array
|
||||
{
|
||||
$composerPath = PathHelper::make($this->kernel->root, 'vendor/composer');
|
||||
$installed = $this->loadJsonFile(PathHelper::make($composerPath, 'installed.json'));
|
||||
$packages = $installed['packages'] ?? [];
|
||||
|
||||
$discoveredLocations = [];
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$packageName = ($package['name'] ?? '');
|
||||
$isTempest = str_starts_with($packageName, 'tempest');
|
||||
|
||||
if ($isTempest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$packagePath = PathHelper::make($composerPath, $package['install-path'] ?? '');
|
||||
$requiresTempest = isset($package['require']['tempest/framework']) || isset($package['require']['tempest/core']);
|
||||
$hasPsr4Namespaces = isset($package['autoload']['psr-4']);
|
||||
|
||||
if (! ($requiresTempest && $hasPsr4Namespaces)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
|
||||
$path = PathHelper::make($packagePath, $namespacePath);
|
||||
|
||||
$discoveredLocations[] = new DiscoveryLocation($namespace, $path);
|
||||
}
|
||||
}
|
||||
|
||||
return $discoveredLocations;
|
||||
}
|
||||
|
||||
private function loadJsonFile(string $path): array
|
||||
{
|
||||
if (! file_exists($path)) {
|
||||
throw new DiscoveryException(sprintf('Could not locate %s, try running "composer install"', $path));
|
||||
}
|
||||
|
||||
return json_decode(file_get_contents($path), true);
|
||||
}
|
||||
}
|
||||
42
src/PHPNative/Framework/src/Lifecycle/Context.php
Normal file
42
src/PHPNative/Framework/src/Lifecycle/Context.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Lifecycle;
|
||||
|
||||
use PHPNative\Event\Event;
|
||||
use PHPNative\Framework\Application\Window;
|
||||
use PHPNative\Renderer\Thread;
|
||||
|
||||
class Context
|
||||
{
|
||||
|
||||
public function __construct(private Thread $thread)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function show(Window $window): void
|
||||
{
|
||||
$this->thread->show($window);
|
||||
}
|
||||
|
||||
public function update(float $delta): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function event(Event $event): void
|
||||
{
|
||||
$this->thread->addEvent($event);
|
||||
}
|
||||
|
||||
public function render(float $delta): void
|
||||
{
|
||||
$this->thread->render();
|
||||
}
|
||||
|
||||
public function unload(): void
|
||||
{
|
||||
//TODO: unload
|
||||
}
|
||||
|
||||
}
|
||||
15
src/PHPNative/Framework/src/Lifecycle/ContextCollection.php
Normal file
15
src/PHPNative/Framework/src/Lifecycle/ContextCollection.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Lifecycle;
|
||||
|
||||
use PHPNative\Core\TypedCollection;
|
||||
|
||||
#[Singleton]
|
||||
class ContextCollection extends TypedCollection
|
||||
{
|
||||
|
||||
protected function type(): string
|
||||
{
|
||||
return Context::class;
|
||||
}
|
||||
}
|
||||
87
src/PHPNative/Framework/src/Lifecycle/Lifecycle.php
Normal file
87
src/PHPNative/Framework/src/Lifecycle/Lifecycle.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Lifecycle;
|
||||
|
||||
use PHPNative\Container\Container;
|
||||
use PHPNative\Event\Event;
|
||||
use PHPNative\Event\EventType;
|
||||
use PHPNative\Framework\Application\Window;
|
||||
use PHPNative\Framework\Loop\OrderedEventLoop;
|
||||
use PHPNative\Framework\Loop\WorkerInterface;
|
||||
use PHPNative\Renderer\Thread;
|
||||
|
||||
class Lifecycle implements WorkerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private OrderedEventLoop $loop,
|
||||
private ContextCollection $contextCollection,
|
||||
private Container $container)
|
||||
{
|
||||
$this->loop->use($this);
|
||||
}
|
||||
|
||||
public function show(Window $window, array $arguments = []): void
|
||||
{
|
||||
|
||||
$context = $this->container->get(
|
||||
Context::class,
|
||||
thread: $this->container->get(Thread::class)
|
||||
);
|
||||
|
||||
$context->show($window);
|
||||
|
||||
$this->contextCollection->add($context);
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function onUpdate(float $delta): void
|
||||
{
|
||||
$this->contextCollection->map(fn(Context $context) => $context->update($delta));
|
||||
}
|
||||
|
||||
public function onRender(float $delta): void
|
||||
{
|
||||
$this->contextCollection->map(fn(Context $context) => $context->render($delta));
|
||||
}
|
||||
|
||||
public function onEvent( $event): void
|
||||
{
|
||||
$this->defaultEventLogic($event);
|
||||
$this->contextCollection->map(fn(Context $context) => $context->event($event));
|
||||
}
|
||||
|
||||
protected function defaultEventLogic(Event $event): void
|
||||
{
|
||||
switch ($event->getType()) {
|
||||
case EventType::WINDOW_FOCUS_LOST:
|
||||
$this->loop->pause();
|
||||
break;
|
||||
case EventType::WINDOW_FOCUS_GAINED:
|
||||
$this->loop->resume();
|
||||
break;
|
||||
case EventType::QUIT:
|
||||
$this->contextCollection->map(fn(Context $context) => $context->unload());
|
||||
$this->contextCollection = new ContextCollection();
|
||||
$this->loop->stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function onPause(): void
|
||||
{
|
||||
if ($this->context !== null) {
|
||||
$this->context->pause();
|
||||
}
|
||||
}
|
||||
|
||||
public function onResume(): void
|
||||
{
|
||||
if ($this->context !== null) {
|
||||
$this->context->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/PHPNative/Framework/src/Loop/EventLoop.php
Normal file
82
src/PHPNative/Framework/src/Loop/EventLoop.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Loop;
|
||||
|
||||
use PHPNative\Event\Event;
|
||||
|
||||
abstract class EventLoop implements LoopInterface
|
||||
{
|
||||
|
||||
protected bool $running = false;
|
||||
protected bool $paused = false;
|
||||
|
||||
private ?WorkerInterface $worker = null;
|
||||
|
||||
public function use(?WorkerInterface $worker): void
|
||||
{
|
||||
$this->worker = $worker;
|
||||
}
|
||||
|
||||
public function run(int $frameRate = self::DEFAULT_FRAME_RATE, int $updateRate = self::DEFAULT_UPDATE_RATE): void
|
||||
{
|
||||
|
||||
$this->paused = false;
|
||||
|
||||
if ($this->running) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->running = true;
|
||||
|
||||
$this->execute($frameRate, $updateRate);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function execute(int $frameRate, int $updateRate): void;
|
||||
|
||||
public function pause(): void
|
||||
{
|
||||
if ($this->paused === false && $this->worker !== null) {
|
||||
$this->worker->onPause();
|
||||
}
|
||||
|
||||
$this->paused = true;
|
||||
}
|
||||
|
||||
public function resume(): void
|
||||
{
|
||||
if ($this->paused === true && $this->worker !== null) {
|
||||
$this->worker->onResume();
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
}
|
||||
public function stop(): void
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
protected function render(float $delta): void
|
||||
{
|
||||
if ($this->worker !== null) {
|
||||
$this->worker->onRender($delta);
|
||||
}
|
||||
}
|
||||
|
||||
protected function update(float $delta): void
|
||||
{
|
||||
if ($this->worker !== null && $this->paused === false) {
|
||||
$this->worker->onUpdate($delta);
|
||||
}
|
||||
}
|
||||
|
||||
protected function poll(Event $event): void
|
||||
{
|
||||
if ($this->worker !== null && $this->paused === false) {
|
||||
$this->worker->onEvent($event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
src/PHPNative/Framework/src/Loop/LoopInterface.php
Normal file
18
src/PHPNative/Framework/src/Loop/LoopInterface.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Loop;
|
||||
|
||||
interface LoopInterface
|
||||
{
|
||||
public const DEFAULT_FRAME_RATE = 60;
|
||||
|
||||
public const DEFAULT_UPDATE_RATE = 60;
|
||||
|
||||
public function run(int $frameRate = self::DEFAULT_FRAME_RATE, int $updateRate = self::DEFAULT_UPDATE_RATE): void;
|
||||
|
||||
public function pause(): void;
|
||||
|
||||
public function resume(): void;
|
||||
|
||||
public function stop(): void;
|
||||
}
|
||||
42
src/PHPNative/Framework/src/Loop/OrderedEventLoop.php
Normal file
42
src/PHPNative/Framework/src/Loop/OrderedEventLoop.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Loop;
|
||||
|
||||
use PHPNative\Event\Driver;
|
||||
use PHPNative\Event\EventType;
|
||||
|
||||
class OrderedEventLoop extends EventLoop
|
||||
{
|
||||
public Timer $render;
|
||||
|
||||
public Timer $updates;
|
||||
|
||||
public function __construct(private Driver $eventDriver)
|
||||
{
|
||||
$this->render = new Timer(self::DEFAULT_FRAME_RATE);
|
||||
$this->updates = new Timer(self::DEFAULT_UPDATE_RATE);
|
||||
}
|
||||
|
||||
protected function execute(int $frameRate, int $updateRate): void
|
||||
{
|
||||
$this->render->rate($frameRate)->touch();
|
||||
$this->updates->rate($updateRate)->touch();
|
||||
|
||||
while ($this->running) {
|
||||
$now = \microtime(true);
|
||||
|
||||
if (($delta = $this->updates->next($now)) !== null) {
|
||||
$this->update($delta);
|
||||
}
|
||||
|
||||
if (($delta = $this->render->next($now)) !== null) {
|
||||
$this->render($delta);
|
||||
}
|
||||
|
||||
while ($event = $this->eventDriver->pollEvent()) {
|
||||
if($event->getType() == EventType::NOOP) break;
|
||||
$this->poll($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Framework\src\Loop;
|
||||
namespace PHPNative\Framework\Loop;
|
||||
|
||||
use function microtime;
|
||||
|
||||
class Timer
|
||||
{
|
||||
@ -20,10 +23,9 @@ class Timer
|
||||
|
||||
public function touch(float $now = null): void
|
||||
{
|
||||
$this->time = $now ?? \microtime(true);
|
||||
$this->time = $now ?? microtime(true);
|
||||
}
|
||||
|
||||
|
||||
public function rate(int $rate): self
|
||||
{
|
||||
$this->rate = $rate === 0 ? 0 : 1 / $rate;
|
||||
|
||||
14
src/PHPNative/Framework/src/Loop/WorkerInterface.php
Normal file
14
src/PHPNative/Framework/src/Loop/WorkerInterface.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\Loop;
|
||||
|
||||
interface WorkerInterface
|
||||
{
|
||||
public function onUpdate(float $delta): void;
|
||||
|
||||
public function onRender(float $delta): void;
|
||||
|
||||
public function onPause(): void;
|
||||
|
||||
public function onResume(): void;
|
||||
}
|
||||
@ -1,16 +1,24 @@
|
||||
<?PHP
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework\src;
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPNative\Framework\src\Application\Kernel;
|
||||
namespace PHPNative\Framework;
|
||||
|
||||
final class PHPNative {
|
||||
use PHPNative\Container\Container;
|
||||
use PHPNative\Framework\Application\Kernel;
|
||||
|
||||
public static function boot(string $directory): Kernel
|
||||
{
|
||||
|
||||
return new Kernel($directory);
|
||||
final class PHPNative
|
||||
{
|
||||
public static function boot(
|
||||
?string $root = null,
|
||||
array $discoveryLocations = [],
|
||||
): Container {
|
||||
$root ??= getcwd();
|
||||
|
||||
// Kernel
|
||||
return (new Kernel(
|
||||
root: $root,
|
||||
discoveryLocations: $discoveryLocations,
|
||||
))->container;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
src/PHPNative/Renderer/composer.json
Normal file
17
src/PHPNative/Renderer/composer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "phpnative/renderer",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Renderer\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Renderer\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/PHPNative/Renderer/src/Thread.php
Normal file
84
src/PHPNative/Renderer/src/Thread.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Renderer;
|
||||
|
||||
use PHPNative\Event\Event;
|
||||
use PHPNative\Event\EventCollection;
|
||||
use PHPNative\Framework\Application\Window;
|
||||
use PHPNative\Tailwind\Style\Background;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
|
||||
class Thread
|
||||
{
|
||||
private ?\SDL_Window $windowId = null;
|
||||
private $rendererPtr = null;
|
||||
|
||||
private Window $window;
|
||||
|
||||
private EventCollection $eventStack;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->eventStack = new EventCollection();
|
||||
}
|
||||
|
||||
public function show(Window $window): void
|
||||
{
|
||||
$this->window = $window;
|
||||
|
||||
\SDL_Init(SDL_INIT_VIDEO);
|
||||
\SDL_TTF_Init();
|
||||
$this->windowId = \SDL_CreateWindow($this->window->getTitle(), 800, 600, \SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
$this->rendererPtr = \SDL_CreateRenderer($this->windowId);
|
||||
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
\SDL_DestroyRenderer($this->rendererPtr);
|
||||
\SDL_DestroyWindow($this->windowId);
|
||||
\SDL_Quit();
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
$windowWidth = 0;
|
||||
$windowHeight = 0;
|
||||
\SDL_GetWindowSize($this->windowId, $windowWidth, $windowHeight);
|
||||
|
||||
$viewPort = new Viewport($this->windowId, $this->rendererPtr, 0, 0, $windowWidth, $windowHeight, $windowWidth, $windowHeight, MediaQueryEnum::getFromPixel($windowWidth));
|
||||
|
||||
$this->startRender();
|
||||
|
||||
Widget::render($this, $viewPort, $this->window->getView());
|
||||
|
||||
$this->endRender();
|
||||
$this->eventStack->removeFirstItem();
|
||||
}
|
||||
|
||||
private function startRender(): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function endRender(): void
|
||||
{
|
||||
\SDL_RenderPresent($this->rendererPtr);
|
||||
}
|
||||
|
||||
public function addEvent(Event $event): void
|
||||
{
|
||||
$this->eventStack->removeItem($event);
|
||||
$this->eventStack->add($event);
|
||||
}
|
||||
|
||||
public function getEvent(): ?Event
|
||||
{
|
||||
if($this->eventStack->count() > 0) {
|
||||
return $this->eventStack->first();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
25
src/PHPNative/Renderer/src/Viewport.php
Normal file
25
src/PHPNative/Renderer/src/Viewport.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Renderer;
|
||||
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
|
||||
class Viewport
|
||||
{
|
||||
|
||||
public function __construct(public $windowId,
|
||||
public $renderPtr,
|
||||
public int $x = 0,
|
||||
public int $y = 0,
|
||||
public $width = 0,
|
||||
public $height = 0,
|
||||
public $windowWidth = 0,
|
||||
public $windowHeight = 0,
|
||||
public MediaQueryEnum $windowMediaQuery = MediaQueryEnum::normal)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
26
src/PHPNative/Renderer/src/Widget.php
Normal file
26
src/PHPNative/Renderer/src/Widget.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Renderer;
|
||||
|
||||
use PHPNative\UI\BaseView;
|
||||
use PHPNative\UI\View;
|
||||
use PHPNative\UI\Widget\Button;
|
||||
use PHPNative\UI\Widget\Container;
|
||||
|
||||
class Widget
|
||||
{
|
||||
public static function render(Thread $thread, ViewPort $viewPort, View $view): Viewport {
|
||||
|
||||
if($view instanceof BaseView) {
|
||||
return \PHPNative\Renderer\Widgets\BaseView::render($thread, $viewPort, $view);
|
||||
}
|
||||
if($view instanceof Button) {
|
||||
return \PHPNative\Renderer\Widgets\Button::render($thread, $viewPort, $view);
|
||||
}
|
||||
if($view instanceof Container) {
|
||||
return \PHPNative\Renderer\Widgets\Container::render($thread, $viewPort, $view);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
47
src/PHPNative/Renderer/src/Widgets/BaseView.php
Normal file
47
src/PHPNative/Renderer/src/Widgets/BaseView.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Renderer\Widgets;
|
||||
|
||||
use PHPNative\Renderer\Thread;
|
||||
use PHPNative\Renderer\Viewport;
|
||||
use PHPNative\Renderer\Widget;
|
||||
use PHPNative\Tailwind\Style\Background;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
|
||||
class BaseView
|
||||
{
|
||||
public static function render(Thread $thread, Viewport $viewport, \PHPNative\UI\BaseView $view): Viewport
|
||||
{
|
||||
$styles = StyleParser::parse($view->style)->getValidStyles($viewport->windowMediaQuery, StateEnum::normal);
|
||||
|
||||
if(isset($styles[Margin::class]) && $m = $styles[Margin::class]) {
|
||||
$viewport->x += $m->left;
|
||||
$viewport->width -= ($m->right + $m->left);
|
||||
$viewport->y += $m->top;
|
||||
$viewport->height -= ($m->bottom + $m->top);
|
||||
}
|
||||
|
||||
if(isset($styles[Background::class]) && $bg = $styles[Background::class]) {
|
||||
$rect = new \SDL_FRect($viewport->x, $viewport->y, $viewport->width, $viewport->height);
|
||||
\SDL_SetRenderDrawColor($viewport->renderPtr, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha);
|
||||
\SDL_RenderFillRect($viewport->renderPtr, $rect);
|
||||
}
|
||||
|
||||
if(isset($styles[Padding::class]) && $m = $styles[Padding::class]) {
|
||||
$viewport->x += $m->left;
|
||||
$viewport->width -= ($m->right + $m->left);
|
||||
$viewport->y += $m->top;
|
||||
$viewport->height -= ($m->bottom + $m->top);
|
||||
}
|
||||
|
||||
if($view->getView() !== null) {
|
||||
Widget::render($thread, $viewport, $view->getView());
|
||||
}
|
||||
|
||||
return $viewport;
|
||||
}
|
||||
}
|
||||
90
src/PHPNative/Renderer/src/Widgets/Button.php
Normal file
90
src/PHPNative/Renderer/src/Widgets/Button.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Renderer\Widgets;
|
||||
|
||||
use PHPNative\Event\EventType;
|
||||
use PHPNative\Renderer\Thread;
|
||||
use PHPNative\Renderer\Viewport;
|
||||
use PHPNative\Renderer\Widget;
|
||||
use PHPNative\Tailwind\Style\Background;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
use PHPNative\Tailwind\Style\Unit;
|
||||
use PHPNative\Tailwind\Style\Width;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
|
||||
class Button
|
||||
{
|
||||
|
||||
public static function render(Thread $thread, Viewport $viewport, \PHPNative\UI\Widget\Button $view): Viewport
|
||||
{
|
||||
$styles = StyleParser::parse($view->style)->getValidStyles($viewport->windowMediaQuery, $view->state);
|
||||
|
||||
$font = \SDL_TTF_OpenFont(__DIR__ . DIRECTORY_SEPARATOR . '../../../../../assets/segoe-ui.ttf' , 30);
|
||||
|
||||
$color = new \SDL_Color(0,0,0,0);
|
||||
|
||||
$surface = \SDL_TTF_RenderText_Blended($font, $view->label, $color);
|
||||
$texture = \SDL_CreateTextureFromSurface($viewport->renderPtr, $surface);
|
||||
|
||||
if(isset($styles[Width::class]) && $m = $styles[Width::class]) {
|
||||
if($styles[Width::class]->unit === Unit::Pixel) {
|
||||
$viewport->width = $styles[Width::class]->value;
|
||||
}elseif($styles[Width::class]->unit === Unit::Percent) {
|
||||
$viewport->width = $viewport->width/100*$styles[Width::class]->value;
|
||||
}
|
||||
|
||||
}else{
|
||||
$viewport->width = $surface->w;
|
||||
$viewport->height = $surface->h;
|
||||
}
|
||||
|
||||
if(isset($styles[Margin::class]) && $m = $styles[Margin::class]) {
|
||||
$viewport->x += $m->left;
|
||||
$viewport->y += $m->top;
|
||||
$viewport->width += $m->right;
|
||||
$viewport->height += $m->bottom;
|
||||
}
|
||||
|
||||
$bgX = $viewport->x;
|
||||
$bgY = $viewport->y;
|
||||
|
||||
if(isset($styles[Padding::class]) && $p = $styles[Padding::class]) {
|
||||
$viewport->x += $p->left;
|
||||
$viewport->width += ($p->right + $p->left);
|
||||
$viewport->y += $p->top;
|
||||
$viewport->height += ($p->bottom + $p->top);
|
||||
}
|
||||
|
||||
if(isset($styles[Background::class]) && $bg = $styles[Background::class]) {
|
||||
$rect = new \SDL_FRect($bgX, $bgY, $viewport->width, $viewport->height);
|
||||
\SDL_SetRenderDrawColor($viewport->renderPtr, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha);
|
||||
\SDL_RenderFillRect($viewport->renderPtr, $rect);
|
||||
}
|
||||
|
||||
if($thread->getEvent() && $thread->getEvent()->getType() === EventType::MOUSEMOVE) {
|
||||
if( $viewport->x <= $thread->getEvent()->x &&
|
||||
$thread->getEvent()->x <= $viewport->x + $viewport->width &&
|
||||
$viewport->y <= $thread->getEvent()->y &&
|
||||
$thread->getEvent()->y <= $viewport->y + $viewport->height ) {
|
||||
$view->state = StateEnum::hover;
|
||||
}else{
|
||||
$view->state = StateEnum::normal;
|
||||
}
|
||||
}
|
||||
|
||||
if($thread->getEvent() && $thread->getEvent()->getType() === EventType::MOUSEBUTTON_UP) {
|
||||
if( $viewport->x <= $thread->getEvent()->x && $thread->getEvent()->x <= $viewport->x + $viewport->width && $viewport->y <= $thread->getEvent()->y && $thread->getEvent()->y <= $viewport->y + $viewport->height ) {
|
||||
$view->onClick();
|
||||
}
|
||||
}
|
||||
|
||||
$rect = new \SDL_FRect($viewport->x,$viewport->y,$surface->w,$surface->h);
|
||||
\SDL_RenderTexture($viewport->renderPtr, $texture, null, $rect);
|
||||
|
||||
return $viewport;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/PHPNative/Renderer/src/Widgets/Container.php
Normal file
46
src/PHPNative/Renderer/src/Widgets/Container.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Renderer\Widgets;
|
||||
|
||||
use PHPNative\Renderer\Thread;
|
||||
use PHPNative\Renderer\Viewport;
|
||||
use PHPNative\Renderer\Widget;
|
||||
use PHPNative\Tailwind\Style\Background;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
|
||||
class Container
|
||||
{
|
||||
public static function render(Thread $thread, Viewport $viewport, \PHPNative\UI\Widget\Container $view): Viewport
|
||||
{
|
||||
$styles = StyleParser::parse($view->style)->getValidStyles($viewport->windowMediaQuery, StateEnum::normal);
|
||||
|
||||
if(isset($styles[Margin::class]) && $m = $styles[Margin::class]) {
|
||||
$viewport->x += $m->left;
|
||||
$viewport->width -= ($m->right + $m->left);
|
||||
$viewport->y += $m->top;
|
||||
$viewport->height -= ($m->bottom + $m->top);
|
||||
}
|
||||
|
||||
if(isset($styles[Background::class]) && $bg = $styles[Background::class]) {
|
||||
$rect = new \SDL_FRect($viewport->x, $viewport->y, $viewport->width, $viewport->height);
|
||||
\SDL_SetRenderDrawColor($viewport->renderPtr, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha);
|
||||
\SDL_RenderFillRect($viewport->renderPtr, $rect);
|
||||
}
|
||||
|
||||
if(isset($styles[Padding::class]) && $m = $styles[Padding::class]) {
|
||||
$viewport->x += $m->left;
|
||||
$viewport->width -= ($m->right + $m->left);
|
||||
$viewport->y += $m->top;
|
||||
$viewport->height -= ($m->bottom + $m->top);
|
||||
}
|
||||
|
||||
foreach($view->subViews as $subView) {
|
||||
Widget::render($thread, clone $viewport, $subView);
|
||||
}
|
||||
|
||||
return $viewport;
|
||||
}
|
||||
}
|
||||
17
src/PHPNative/Support/composer.json
Normal file
17
src/PHPNative/Support/composer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "phpnative/support",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Support\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPNative\\Support\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/PHPNative/Support/src/Reflection/ClassReflector.php
Normal file
126
src/PHPNative/Support/src/Reflection/ClassReflector.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use Generator;
|
||||
use ReflectionClass as PHPReflectionClass;
|
||||
use ReflectionMethod as PHPReflectionMethod;
|
||||
use ReflectionProperty as PHPReflectionProperty;
|
||||
|
||||
/**
|
||||
* @template TClassName
|
||||
*/
|
||||
final readonly class ClassReflector implements Reflector
|
||||
{
|
||||
use HasAttributes;
|
||||
|
||||
private PHPReflectionClass $reflectionClass;
|
||||
|
||||
/**
|
||||
* @param class-string<TClassName>|object<TClassName>|PHPReflectionClass<TClassName> $reflectionClass
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
public function __construct(string|object $reflectionClass)
|
||||
{
|
||||
if (is_string($reflectionClass)) {
|
||||
$reflectionClass = new PHPReflectionClass($reflectionClass);
|
||||
} elseif (! $reflectionClass instanceof PHPReflectionClass && is_object($reflectionClass)) {
|
||||
$reflectionClass = new PHPReflectionClass($reflectionClass);
|
||||
}
|
||||
|
||||
$this->reflectionClass = $reflectionClass;
|
||||
}
|
||||
|
||||
public function getReflection(): PHPReflectionClass
|
||||
{
|
||||
return $this->reflectionClass;
|
||||
}
|
||||
|
||||
/** @return Generator<PropertyReflector> */
|
||||
public function getPublicProperties(): Generator
|
||||
{
|
||||
foreach ($this->reflectionClass->getProperties(PHPReflectionProperty::IS_PUBLIC) as $property) {
|
||||
yield new PropertyReflector($property);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return Generator<MethodReflector> */
|
||||
public function getPublicMethods(): Generator
|
||||
{
|
||||
foreach ($this->reflectionClass->getMethods(PHPReflectionMethod::IS_PUBLIC) as $method) {
|
||||
yield new MethodReflector($method);
|
||||
}
|
||||
}
|
||||
|
||||
public function getProperty(string $name): PropertyReflector
|
||||
{
|
||||
return new PropertyReflector(new PHPReflectionProperty($this->reflectionClass->getName(), $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<TClassName>
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflectionClass->getName();
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
return $this->reflectionClass->getShortName();
|
||||
}
|
||||
|
||||
public function getType(): TypeReflector
|
||||
{
|
||||
return new TypeReflector($this->reflectionClass);
|
||||
}
|
||||
|
||||
public function getConstructor(): ?MethodReflector
|
||||
{
|
||||
$constructor = $this->reflectionClass->getConstructor();
|
||||
|
||||
if ($constructor === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MethodReflector($constructor);
|
||||
}
|
||||
|
||||
public function getMethod(string $name): ?MethodReflector
|
||||
{
|
||||
return new MethodReflector($this->reflectionClass->getMethod($name));
|
||||
}
|
||||
|
||||
public function isInstantiable(): bool
|
||||
{
|
||||
return $this->reflectionClass->isInstantiable();
|
||||
}
|
||||
|
||||
public function newInstanceWithoutConstructor(): object
|
||||
{
|
||||
return $this->reflectionClass->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
public function newInstanceArgs(array $args = []): object
|
||||
{
|
||||
return $this->reflectionClass->newInstanceArgs($args);
|
||||
}
|
||||
|
||||
public function callStatic(string $method, mixed ...$args): mixed
|
||||
{
|
||||
$className = $this->getName();
|
||||
|
||||
return $className::$method(...$args);
|
||||
}
|
||||
|
||||
public function is(string $className): bool
|
||||
{
|
||||
return $this->getType()->matches($className);
|
||||
}
|
||||
|
||||
public function implements(string $interface): bool
|
||||
{
|
||||
return $this->isInstantiable() && $this->getType()->matches($interface);
|
||||
}
|
||||
}
|
||||
50
src/PHPNative/Support/src/Reflection/FunctionReflector.php
Normal file
50
src/PHPNative/Support/src/Reflection/FunctionReflector.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use Closure;
|
||||
use Generator;
|
||||
use ReflectionFunction as PHPReflectionFunction;
|
||||
|
||||
final readonly class FunctionReflector implements Reflector
|
||||
{
|
||||
private PHPReflectionFunction $reflectionFunction;
|
||||
|
||||
public function __construct(
|
||||
PHPReflectionFunction|Closure $function
|
||||
) {
|
||||
$this->reflectionFunction = $function instanceof Closure
|
||||
? new PHPReflectionFunction($function)
|
||||
: $function;
|
||||
}
|
||||
|
||||
/** @return Generator|\PHPNative\Support\Reflection\ParameterReflector[] */
|
||||
public function getParameters(): Generator
|
||||
{
|
||||
foreach ($this->reflectionFunction->getParameters() as $parameter) {
|
||||
yield new ParameterReflector($parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflectionFunction->getName();
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
return $this->reflectionFunction->getShortName();
|
||||
}
|
||||
|
||||
public function getFileName(): string
|
||||
{
|
||||
return $this->reflectionFunction->getFileName();
|
||||
}
|
||||
|
||||
public function getStartLine(): int
|
||||
{
|
||||
return (int) $this->reflectionFunction->getStartLine();
|
||||
}
|
||||
}
|
||||
46
src/PHPNative/Support/src/Reflection/HasAttributes.php
Normal file
46
src/PHPNative/Support/src/Reflection/HasAttributes.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use ReflectionAttribute as PHPReflectionAttribute;
|
||||
use ReflectionClass as PHPReflectionClass;
|
||||
use ReflectionMethod as PHPReflectionMethod;
|
||||
use ReflectionParameter as PHPReflectionParameter;
|
||||
use ReflectionProperty as PHPReflectionProperty;
|
||||
|
||||
trait HasAttributes
|
||||
{
|
||||
abstract public function getReflection(): PHPReflectionClass|PHPReflectionMethod|PHPReflectionProperty|PHPReflectionParameter;
|
||||
|
||||
public function hasAttribute(string $name): bool
|
||||
{
|
||||
return $this->getReflection()->getAttributes($name) !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TAttributeClass of object
|
||||
* @param class-string<TAttributeClass> $attributeClass
|
||||
* @return TAttributeClass|null
|
||||
*/
|
||||
public function getAttribute(string $attributeClass): object|null
|
||||
{
|
||||
$attribute = $this->getReflection()->getAttributes($attributeClass, PHPReflectionAttribute::IS_INSTANCEOF)[0] ?? null;
|
||||
|
||||
return $attribute?->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TAttributeClass of object
|
||||
* @param class-string<TAttributeClass> $attributeClass
|
||||
* @return TAttributeClass[]
|
||||
*/
|
||||
public function getAttributes(string $attributeClass): array
|
||||
{
|
||||
return array_map(
|
||||
fn (PHPReflectionAttribute $attribute) => $attribute->newInstance(),
|
||||
$this->getReflection()->getAttributes($attributeClass, PHPReflectionAttribute::IS_INSTANCEOF)
|
||||
);
|
||||
}
|
||||
}
|
||||
87
src/PHPNative/Support/src/Reflection/MethodReflector.php
Normal file
87
src/PHPNative/Support/src/Reflection/MethodReflector.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use Generator;
|
||||
use ReflectionMethod as PHPReflectionMethod;
|
||||
|
||||
final readonly class MethodReflector implements Reflector
|
||||
{
|
||||
use HasAttributes;
|
||||
|
||||
public function __construct(
|
||||
private PHPReflectionMethod $reflectionMethod,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromParts(string|object $class, string $name): self
|
||||
{
|
||||
return new self(new PHPReflectionMethod($class, $name));
|
||||
}
|
||||
|
||||
public function getReflection(): PHPReflectionMethod
|
||||
{
|
||||
return $this->reflectionMethod;
|
||||
}
|
||||
|
||||
/** @return Generator|\Tempest\Support\Reflection\ParameterReflector[] */
|
||||
public function getParameters(): Generator
|
||||
{
|
||||
foreach ($this->reflectionMethod->getParameters() as $parameter) {
|
||||
yield new ParameterReflector($parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public function invokeArgs(object|null $object, array $args = []): mixed
|
||||
{
|
||||
return $this->reflectionMethod->invokeArgs($object, $args);
|
||||
}
|
||||
|
||||
public function getReturnType(): TypeReflector
|
||||
{
|
||||
return new TypeReflector($this->reflectionMethod->getReturnType());
|
||||
}
|
||||
|
||||
public function getDeclaringClass(): ClassReflector
|
||||
{
|
||||
return new ClassReflector($this->reflectionMethod->getDeclaringClass());
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflectionMethod->getName();
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
$string = $this->getDeclaringClass()->getShortName() . '::' . $this->getName() . '(';
|
||||
|
||||
$parameters = [];
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$parameters[] = $parameter->getType()->getShortName() . ' $' . $parameter->getName();
|
||||
}
|
||||
|
||||
$string .= implode(', ', $parameters);
|
||||
|
||||
return $string . ')';
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->reflectionMethod->getDeclaringClass()->getName(),
|
||||
'method' => $this->reflectionMethod->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->reflectionMethod = new PHPReflectionMethod(
|
||||
objectOrMethod: $data['class'],
|
||||
method: $data['method'],
|
||||
);
|
||||
}
|
||||
}
|
||||
73
src/PHPNative/Support/src/Reflection/ParameterReflector.php
Normal file
73
src/PHPNative/Support/src/Reflection/ParameterReflector.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use ReflectionParameter as PHPReflectionParameter;
|
||||
|
||||
final readonly class ParameterReflector implements Reflector
|
||||
{
|
||||
use HasAttributes;
|
||||
|
||||
public function __construct(
|
||||
private PHPReflectionParameter $reflectionParameter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getReflection(): PHPReflectionParameter
|
||||
{
|
||||
return $this->reflectionParameter;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflectionParameter->getName();
|
||||
}
|
||||
|
||||
public function getType(): TypeReflector
|
||||
{
|
||||
return new TypeReflector($this->reflectionParameter);
|
||||
}
|
||||
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return $this->reflectionParameter->isOptional();
|
||||
}
|
||||
|
||||
public function isDefaultValueAvailable(): bool
|
||||
{
|
||||
return $this->reflectionParameter->isDefaultValueAvailable();
|
||||
}
|
||||
|
||||
public function getPosition(): int
|
||||
{
|
||||
return $this->reflectionParameter->getPosition();
|
||||
}
|
||||
|
||||
public function hasDefaultValue(): bool
|
||||
{
|
||||
return $this->reflectionParameter->isDefaultValueAvailable();
|
||||
}
|
||||
|
||||
public function getDefaultValue(): mixed
|
||||
{
|
||||
return $this->reflectionParameter->getDefaultValue();
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->reflectionParameter->isVariadic();
|
||||
}
|
||||
|
||||
public function isIterable(): bool
|
||||
{
|
||||
return $this->getType()->isIterable();
|
||||
}
|
||||
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return ! $this->reflectionParameter->allowsNull()
|
||||
&& ! $this->reflectionParameter->isOptional();
|
||||
}
|
||||
}
|
||||
130
src/PHPNative/Support/src/Reflection/PropertyReflector.php
Normal file
130
src/PHPNative/Support/src/Reflection/PropertyReflector.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use Error;
|
||||
use ReflectionProperty as PHPReflectionProperty;
|
||||
|
||||
final readonly class PropertyReflector implements Reflector
|
||||
{
|
||||
use HasAttributes;
|
||||
|
||||
public function __construct(
|
||||
private PHPReflectionProperty $reflectionProperty,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromParts(string|object $class, string $name): self
|
||||
{
|
||||
return new self(new PHPReflectionProperty($class, $name));
|
||||
}
|
||||
|
||||
public function getReflection(): PHPReflectionProperty
|
||||
{
|
||||
return $this->reflectionProperty;
|
||||
}
|
||||
|
||||
public function getValue(object $object): mixed
|
||||
{
|
||||
return $this->reflectionProperty->getValue($object);
|
||||
}
|
||||
|
||||
public function setValue(object $object, mixed $value): void
|
||||
{
|
||||
$this->reflectionProperty->setValue($object, $value);
|
||||
}
|
||||
|
||||
public function isInitialized(object $object): bool
|
||||
{
|
||||
return $this->reflectionProperty->isInitialized($object);
|
||||
}
|
||||
|
||||
public function accepts(mixed $input): bool
|
||||
{
|
||||
return $this->getType()->accepts($input);
|
||||
}
|
||||
|
||||
public function getClass(): ClassReflector
|
||||
{
|
||||
return new ClassReflector($this->reflectionProperty->getDeclaringClass());
|
||||
}
|
||||
|
||||
public function getType(): ?TypeReflector
|
||||
{
|
||||
return new TypeReflector($this->reflectionProperty);
|
||||
}
|
||||
|
||||
public function isIterable(): bool
|
||||
{
|
||||
return $this->getType()->isIterable();
|
||||
}
|
||||
|
||||
public function isPromoted(): bool
|
||||
{
|
||||
return $this->reflectionProperty->isPromoted();
|
||||
}
|
||||
|
||||
public function getIterableType(): ?TypeReflector
|
||||
{
|
||||
$doc = $this->reflectionProperty->getDocComment();
|
||||
|
||||
if (! $doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
preg_match('/@var ([\\\\\w]+)\[]/', $doc, $match);
|
||||
|
||||
if (! isset($match[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TypeReflector(ltrim($match[1], '\\'));
|
||||
}
|
||||
|
||||
public function isUninitialized(object $object): bool
|
||||
{
|
||||
return ! $this->reflectionProperty->isInitialized($object);
|
||||
}
|
||||
|
||||
public function unset(object $object): void
|
||||
{
|
||||
unset($object->{$this->getName()});
|
||||
}
|
||||
|
||||
public function set(object $object, mixed $value): void
|
||||
{
|
||||
$this->reflectionProperty->setValue($object, $value);
|
||||
}
|
||||
|
||||
public function get(object $object, mixed $default = null): mixed
|
||||
{
|
||||
try {
|
||||
return $this->reflectionProperty->getValue($object) ?? $default;
|
||||
} catch (Error $error) {
|
||||
return $default ?? throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->reflectionProperty->getName();
|
||||
}
|
||||
|
||||
public function hasDefaultValue(): bool
|
||||
{
|
||||
$constructorParameters = [];
|
||||
|
||||
foreach (($this->getClass()->getConstructor()?->getParameters() ?? []) as $parameter) {
|
||||
$constructorParameters[$parameter->getName()] = $parameter;
|
||||
}
|
||||
|
||||
$hasDefaultValue = $this->reflectionProperty->hasDefaultValue();
|
||||
|
||||
$hasPromotedDefaultValue = $this->isPromoted()
|
||||
&& $constructorParameters[$this->getName()]->isDefaultValueAvailable();
|
||||
|
||||
return $hasDefaultValue || $hasPromotedDefaultValue;
|
||||
}
|
||||
}
|
||||
10
src/PHPNative/Support/src/Reflection/Reflector.php
Normal file
10
src/PHPNative/Support/src/Reflection/Reflector.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
interface Reflector
|
||||
{
|
||||
public function getName(): string;
|
||||
}
|
||||
152
src/PHPNative/Support/src/Reflection/TypeReflector.php
Normal file
152
src/PHPNative/Support/src/Reflection/TypeReflector.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Support\Reflection;
|
||||
|
||||
use Exception;
|
||||
use Generator;
|
||||
use ReflectionClass as PHPReflectionClass;
|
||||
use ReflectionIntersectionType as PHPReflectionIntersectionType;
|
||||
use ReflectionNamedType as PHPReflectionNamedType;
|
||||
use ReflectionParameter as PHPReflectionParameter;
|
||||
use ReflectionProperty as PHPReflectionProperty;
|
||||
use ReflectionType as PHPReflectionType;
|
||||
use ReflectionUnionType as PHPReflectionUnionType;
|
||||
use Reflector as PHPReflector;
|
||||
use TypeError;
|
||||
|
||||
final readonly class TypeReflector implements Reflector
|
||||
{
|
||||
private string $definition;
|
||||
|
||||
public function __construct(
|
||||
private PHPReflector|PHPReflectionType|string $reflector,
|
||||
) {
|
||||
$this->definition = $this->resolveDefinition($this->reflector);
|
||||
}
|
||||
|
||||
public function asClass(): ClassReflector
|
||||
{
|
||||
return new ClassReflector($this->definition);
|
||||
}
|
||||
|
||||
public function equals(string|TypeReflector $type): bool
|
||||
{
|
||||
if (is_string($type)) {
|
||||
$type = new TypeReflector($type);
|
||||
}
|
||||
|
||||
return $this->definition === $type->definition;
|
||||
}
|
||||
|
||||
public function accepts(mixed $input): bool
|
||||
{
|
||||
$test = eval(sprintf('return fn (%s $input) => $input;', $this->definition));
|
||||
|
||||
try {
|
||||
$test($input);
|
||||
} catch (TypeError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function matches(string $className): bool
|
||||
{
|
||||
return is_a($this->definition, $className, true);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
$parts = explode('\\', $this->definition);
|
||||
|
||||
return $parts[array_key_last($parts)];
|
||||
}
|
||||
|
||||
public function isBuiltIn(): bool
|
||||
{
|
||||
return in_array($this->definition, [
|
||||
'string',
|
||||
'bool',
|
||||
'float',
|
||||
'int',
|
||||
'array',
|
||||
'null',
|
||||
'object',
|
||||
'callable',
|
||||
'resource',
|
||||
'never',
|
||||
'void',
|
||||
'true',
|
||||
'false',
|
||||
]);
|
||||
}
|
||||
|
||||
public function isClass(): bool
|
||||
{
|
||||
return class_exists($this->definition);
|
||||
}
|
||||
|
||||
public function isIterable(): bool
|
||||
{
|
||||
return in_array($this->definition, [
|
||||
'array',
|
||||
'iterable',
|
||||
Generator::class,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return self[] */
|
||||
public function split(): array
|
||||
{
|
||||
return array_map(
|
||||
fn (string $part) => new self($part),
|
||||
preg_split('/[&|]/', $this->definition),
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveDefinition(PHPReflector|PHPReflectionType|string $reflector): string
|
||||
{
|
||||
if (is_string($reflector)) {
|
||||
return $reflector;
|
||||
}
|
||||
|
||||
if (
|
||||
$reflector instanceof PHPReflectionParameter
|
||||
|| $reflector instanceof PHPReflectionProperty
|
||||
) {
|
||||
return $this->resolveDefinition($reflector->getType());
|
||||
}
|
||||
|
||||
if ($reflector instanceof PHPReflectionClass) {
|
||||
return $reflector->getName();
|
||||
}
|
||||
|
||||
if ($reflector instanceof PHPReflectionNamedType) {
|
||||
return $reflector->getName();
|
||||
}
|
||||
|
||||
if ($reflector instanceof PHPReflectionUnionType) {
|
||||
return implode('|', array_map(
|
||||
fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
|
||||
$reflector->getTypes(),
|
||||
));
|
||||
}
|
||||
|
||||
if ($reflector instanceof PHPReflectionIntersectionType) {
|
||||
return implode('&', array_map(
|
||||
fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
|
||||
$reflector->getTypes(),
|
||||
));
|
||||
}
|
||||
|
||||
throw new Exception('Could not resolve type');
|
||||
}
|
||||
}
|
||||
17
src/PHPNative/Tailwind/src/Model/Style.php
Normal file
17
src/PHPNative/Tailwind/src/Model/Style.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Model;
|
||||
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
|
||||
class Style
|
||||
{
|
||||
public function __construct(
|
||||
public \PHPNative\Tailwind\Style\Style $style,
|
||||
public MediaQueryEnum $mediaQuery = MediaQueryEnum::normal,
|
||||
public StateEnum $state = StateEnum::normal
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
47
src/PHPNative/Tailwind/src/Model/StyleCollection.php
Normal file
47
src/PHPNative/Tailwind/src/Model/StyleCollection.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Model;
|
||||
|
||||
use PHPNative\Core\TypedCollection;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
|
||||
class StyleCollection extends TypedCollection
|
||||
{
|
||||
protected function type(): string
|
||||
{
|
||||
return Style::class;
|
||||
}
|
||||
|
||||
public function getValidStyles(MediaQueryEnum $mediaQueryEnum, StateEnum $state): array
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach($this->items() as $style) {
|
||||
if(($style->state == StateEnum::normal || $style->state === $state) && ($style->mediaQuery->value === 0 || $style->mediaQuery->value <= $mediaQueryEnum->value)) {
|
||||
$items[] = $style;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->merge($items);
|
||||
}
|
||||
|
||||
private function merge(array $styles): array
|
||||
{
|
||||
$tmp = [];
|
||||
|
||||
foreach($styles as $style) {
|
||||
if(isset($tmp[$style->style::class]) && $style->style::class === Padding::class) {
|
||||
\PHPNative\Tailwind\Parser\Padding::merge($tmp[$style->style::class], $style->style);
|
||||
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Margin::class) {
|
||||
\PHPNative\Tailwind\Parser\Margin::merge($tmp[$style->style::class], $style->style);
|
||||
}else{
|
||||
$tmp[$style->style::class] = $style->style;
|
||||
}
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class Background implements Parser
|
||||
{
|
||||
|
||||
public static function parse(string $style): \PHPNative\Tailwind\Style\Background
|
||||
{
|
||||
$color = new \PHPNative\Tailwind\Style\Color();
|
||||
|
||||
preg_match_all('/bg-(.*)/', $style, $output_array);
|
||||
if(count($output_array[0]) > 0) {
|
||||
if (count($output_array[0]) > 0) {
|
||||
$colorStyle = $output_array[1][0];
|
||||
$color = Color::parse($colorStyle);
|
||||
|
||||
}
|
||||
|
||||
return new \PHPNative\Tailwind\Style\Background($color);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class Color implements Parser
|
||||
{
|
||||
|
||||
public static function parse(string $style): \PHPNative\Tailwind\Style\Color
|
||||
{
|
||||
$red = 0;
|
||||
@ -14,17 +14,24 @@ class Color implements Parser
|
||||
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../Data/colors.json'), true);
|
||||
|
||||
if($style == "black") {
|
||||
return new \PHPNative\Tailwind\Style\Color(0, 0, 0);
|
||||
}
|
||||
if($style == "white") {
|
||||
return new \PHPNative\Tailwind\Style\Color(255, 255, 255);
|
||||
}
|
||||
|
||||
preg_match_all('/(\w{1,8})/', $style, $output_array);
|
||||
if(count($output_array[0]) > 0) {
|
||||
if (count($output_array[0]) > 0) {
|
||||
$color = (string)$output_array[1][0];
|
||||
list($red, $green, $blue) = sscanf($data[$color]['500'], "#%02x%02x%02x");
|
||||
[$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) {
|
||||
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");
|
||||
[$red, $green, $blue] = sscanf($data[$color][$variant], "#%02x%02x%02x");
|
||||
}
|
||||
|
||||
|
||||
|
||||
78
src/PHPNative/Tailwind/src/Parser/Margin.php
Normal file
78
src/PHPNative/Tailwind/src/Parser/Margin.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class Margin implements Parser
|
||||
{
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Margin
|
||||
{
|
||||
$l = null;
|
||||
$r = null;
|
||||
$t = null;
|
||||
$b = null;
|
||||
|
||||
preg_match_all('/m-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 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('/mx-(\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('/my-(\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('/mt-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$t = (int)$output_array[1][0];
|
||||
}
|
||||
|
||||
preg_match_all('/mb-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$b = (int)$output_array[1][0];
|
||||
}
|
||||
|
||||
preg_match_all('/ml-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$l = (int)$output_array[1][0];
|
||||
}
|
||||
|
||||
preg_match_all('/mr-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$r = (int)$output_array[1][0];
|
||||
}
|
||||
|
||||
if($l != null || $r != null || $t != null || $b != null) {
|
||||
return new \PHPNative\Tailwind\Style\Margin($l, $r, $t, $b);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function merge(\PHPNative\Tailwind\Style\Margin $class, \PHPNative\Tailwind\Style\Margin $style)
|
||||
{
|
||||
if($style->left != null) {
|
||||
$class->left = $style->left;
|
||||
}
|
||||
if($style->right != null) {
|
||||
$class->right = $style->right;
|
||||
}
|
||||
if($style->top != null) {
|
||||
$class->top = $style->top;
|
||||
}
|
||||
if($style->bottom != null) {
|
||||
$class->bottom = $style->bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/PHPNative/Tailwind/src/Parser/MediaQuery.php
Normal file
18
src/PHPNative/Tailwind/src/Parser/MediaQuery.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class MediaQuery
|
||||
{
|
||||
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\MediaQueryEnum
|
||||
{
|
||||
preg_match_all("/^(sm|md|lg|xl|2xl)\:(.*)/", $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$query = strtolower(strrev($output_array[1][0]));
|
||||
return \PHPNative\Tailwind\Style\MediaQueryEnum::{$query};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,52 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class Padding implements Parser
|
||||
{
|
||||
|
||||
public static function parse(string $style): \PHPNative\Tailwind\Style\Padding
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Padding
|
||||
{
|
||||
$l = 0;
|
||||
$r = 0;
|
||||
$t = 0;
|
||||
$b = 0;
|
||||
$l = null;
|
||||
$r = null;
|
||||
$t = null;
|
||||
$b = null;
|
||||
|
||||
preg_match_all('/p-(\d)/', $style, $output_array);
|
||||
if(count($output_array[0]) > 0) {
|
||||
preg_match_all('/p-(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 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) {
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
if($l != null || $r != null || $t != null || $b != null) {
|
||||
return new \PHPNative\Tailwind\Style\Padding($l, $r, $t, $b);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
|
||||
{
|
||||
if($style->left != null) {
|
||||
$class->left = $style->left;
|
||||
}
|
||||
if($style->right != null) {
|
||||
$class->right = $style->right;
|
||||
}
|
||||
if($style->top != null) {
|
||||
$class->top = $style->top;
|
||||
}
|
||||
if($style->bottom != null) {
|
||||
$class->bottom = $style->bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
@ -7,5 +8,5 @@ use PHPNative\Tailwind\Style\Style;
|
||||
|
||||
interface Parser
|
||||
{
|
||||
public static function parse(string $style): Style;
|
||||
public static function parse(string $style): ?Style;
|
||||
}
|
||||
18
src/PHPNative/Tailwind/src/Parser/State.php
Normal file
18
src/PHPNative/Tailwind/src/Parser/State.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class State
|
||||
{
|
||||
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\StateEnum
|
||||
{
|
||||
preg_match_all("/(hover|focus|active)\:(.*)/", $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$query = strtolower($output_array[1][0]);
|
||||
return \PHPNative\Tailwind\Style\StateEnum::{$query};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
60
src/PHPNative/Tailwind/src/Parser/Width.php
Normal file
60
src/PHPNative/Tailwind/src/Parser/Width.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
use PHPNative\Tailwind\Style\Unit;
|
||||
|
||||
class Width implements Parser
|
||||
{
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Width
|
||||
{
|
||||
$value = -1;
|
||||
$unit = Unit::Pixel;
|
||||
$found = false;
|
||||
preg_match_all('/w-(\d*)\/(\d*)/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$value1 = (int)$output_array[1][0];
|
||||
$value2 = (int)$output_array[2][0];
|
||||
$unit = Unit::Percent;
|
||||
$value = 100/$value2*$value1;
|
||||
$found = true;
|
||||
}
|
||||
|
||||
preg_match_all('/w-(\d*)/', $style, $output_array);
|
||||
if (!$found && count($output_array[0]) > 0) {
|
||||
$value = (int)$output_array[1][0];
|
||||
}
|
||||
|
||||
preg_match_all('/w-full/', $style, $output_array);
|
||||
if (!$found && count($output_array[0]) > 0) {
|
||||
$value = 100;
|
||||
$unit = Unit::Percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if($value != -1) {
|
||||
return new \PHPNative\Tailwind\Style\Width($unit, $value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
|
||||
{
|
||||
if($style->left != null) {
|
||||
$class->left = $style->left;
|
||||
}
|
||||
if($style->right != null) {
|
||||
$class->right = $style->right;
|
||||
}
|
||||
if($style->top != null) {
|
||||
$class->top = $style->top;
|
||||
}
|
||||
if($style->bottom != null) {
|
||||
$class->bottom = $style->bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class Background implements Style
|
||||
{
|
||||
|
||||
public function __construct(public Color $color = new Color())
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
12
src/PHPNative/Tailwind/src/Style/Margin.php
Normal file
12
src/PHPNative/Tailwind/src/Style/Margin.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class Margin implements Style
|
||||
{
|
||||
public function __construct(public int|null $left = null, public int|null $right = null, public int|null $top = null, public int|null $bottom = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
10
src/PHPNative/Tailwind/src/Style/MediaQuery.php
Normal file
10
src/PHPNative/Tailwind/src/Style/MediaQuery.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class MediaQuery
|
||||
{
|
||||
public function __construct(public MediaQueryEnum $mediaQuery, public string $restStyle)
|
||||
{
|
||||
}
|
||||
}
|
||||
33
src/PHPNative/Tailwind/src/Style/MediaQueryEnum.php
Normal file
33
src/PHPNative/Tailwind/src/Style/MediaQueryEnum.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
enum MediaQueryEnum: int
|
||||
{
|
||||
case normal = 0;
|
||||
case ms = 640;
|
||||
case dm = 768;
|
||||
case gl = 1024;
|
||||
case lx = 1280;
|
||||
case lx2 = 1536;
|
||||
|
||||
public static function getFromPixel(int $windowWidth)
|
||||
{
|
||||
if($windowWidth > self::lx2->value) {
|
||||
return self::lx2;
|
||||
}
|
||||
if($windowWidth > self::lx->value) {
|
||||
return self::lx;
|
||||
}
|
||||
if($windowWidth > self::gl->value) {
|
||||
return self::gl;
|
||||
}
|
||||
if($windowWidth > self::dm->value) {
|
||||
return self::dm;
|
||||
}
|
||||
if($windowWidth > self::ms->value) {
|
||||
return self::ms;
|
||||
}
|
||||
return self::normal;
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class Padding implements Style
|
||||
{
|
||||
public function __construct(public int $left = 0, public int $right = 0, public int $top = 0, public int $bottom = 0)
|
||||
public function __construct(public int|null $left = null, public int|null $right = null, public int|null $top = null, public int|null $bottom = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
10
src/PHPNative/Tailwind/src/Style/State.php
Normal file
10
src/PHPNative/Tailwind/src/Style/State.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class State
|
||||
{
|
||||
public function __construct(public StateEnum $state, public string $restStyle)
|
||||
{
|
||||
}
|
||||
}
|
||||
11
src/PHPNative/Tailwind/src/Style/StateEnum.php
Normal file
11
src/PHPNative/Tailwind/src/Style/StateEnum.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
enum StateEnum
|
||||
{
|
||||
case normal;
|
||||
case hover;
|
||||
case focus;
|
||||
case active;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
11
src/PHPNative/Tailwind/src/Style/Unit.php
Normal file
11
src/PHPNative/Tailwind/src/Style/Unit.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
enum Unit
|
||||
{
|
||||
case Pixel;
|
||||
case Point;
|
||||
case Percent;
|
||||
}
|
||||
12
src/PHPNative/Tailwind/src/Style/Width.php
Normal file
12
src/PHPNative/Tailwind/src/Style/Width.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class Width implements Style
|
||||
{
|
||||
public function __construct(public Unit $unit = Unit::Pixel, public int $value = 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind;
|
||||
|
||||
use PHPNative\Tailwind\Model\Size;
|
||||
use PHPNative\Tailwind\Model\StyleCollection;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\Style;
|
||||
|
||||
class StyleParser
|
||||
{
|
||||
|
||||
public static function parse($style): Style
|
||||
public static function parse($style, int $width = 0): StyleCollection
|
||||
{
|
||||
return new Padding();
|
||||
$computed = new StyleCollection();
|
||||
$styles = explode(" ", $style);
|
||||
|
||||
foreach($styles as $styleStr) {
|
||||
$styleStr = trim($styleStr);
|
||||
|
||||
$style = self::parseSimpleStyle($styleStr);
|
||||
$s = new \PHPNative\Tailwind\Model\Style($style);
|
||||
$mq = \PHPNative\Tailwind\Parser\MediaQuery::parse($styleStr);
|
||||
if($mq) {
|
||||
$s->mediaQuery = $mq;
|
||||
}
|
||||
$state = \PHPNative\Tailwind\Parser\State::parse($styleStr);
|
||||
if($state) {
|
||||
$s->state = $state;
|
||||
}
|
||||
$computed->add($s);
|
||||
}
|
||||
return $computed;
|
||||
}
|
||||
|
||||
private static function parseSimpleStyle(string $style): ?Style
|
||||
{
|
||||
if($pd = \PHPNative\Tailwind\Parser\Padding::parse($style)) {
|
||||
return $pd;
|
||||
}
|
||||
if($m = \PHPNative\Tailwind\Parser\Margin::parse($style)) {
|
||||
return $m;
|
||||
}
|
||||
if($w = \PHPNative\Tailwind\Parser\Width::parse($style)) {
|
||||
return $w;
|
||||
}
|
||||
if($bg = \PHPNative\Tailwind\Parser\Background::parse($style)) {
|
||||
return $bg;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
@ -6,10 +7,13 @@ namespace PHPNative\Tailwind\Tests;
|
||||
use PHPNative\Tailwind\Parser\Background;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @small
|
||||
*/
|
||||
class BackgroundTest extends TestCase
|
||||
{
|
||||
|
||||
public function testBackground(): void
|
||||
public function test_background(): void
|
||||
{
|
||||
$bg = Background::parse("bg-slate-300");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Background::class, $bg);
|
||||
@ -18,5 +22,4 @@ class BackgroundTest extends TestCase
|
||||
$this->assertSame(225, $bg->color->blue);
|
||||
$this->assertSame(0, $bg->color->alpha);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
@ -6,25 +7,49 @@ namespace PHPNative\Tailwind\Tests;
|
||||
use PHPNative\Tailwind\Parser\Color;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @small
|
||||
*/
|
||||
class ColorTest extends TestCase
|
||||
{
|
||||
public function testSimpleName(): void
|
||||
public function test_simple_name(): void
|
||||
{
|
||||
$color = Color::parse("red");
|
||||
self::assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color);
|
||||
self::assertSame(239, $color->red);
|
||||
self::assertSame(68, $color->green);
|
||||
self::assertSame(68, $color->blue);
|
||||
self::assertSame(0, $color->alpha);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color);
|
||||
$this->assertSame(239, $color->red);
|
||||
$this->assertSame(68, $color->green);
|
||||
$this->assertSame(68, $color->blue);
|
||||
$this->assertSame(0, $color->alpha);
|
||||
}
|
||||
|
||||
public function testNameVariant(): void
|
||||
public function test_name_variant(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color);
|
||||
$this->assertSame(190, $color->red);
|
||||
$this->assertSame(242, $color->green);
|
||||
$this->assertSame(100, $color->blue);
|
||||
$this->assertSame(0, $color->alpha);
|
||||
}
|
||||
|
||||
public function test_white(): void
|
||||
{
|
||||
$color = Color::parse("white");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color);
|
||||
$this->assertSame(255, $color->red);
|
||||
$this->assertSame(255, $color->green);
|
||||
$this->assertSame(255, $color->blue);
|
||||
$this->assertSame(0, $color->alpha);
|
||||
}
|
||||
|
||||
public function test_black(): void
|
||||
{
|
||||
$color = Color::parse("black");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Color::class, $color);
|
||||
$this->assertSame(0, $color->red);
|
||||
$this->assertSame(0, $color->green);
|
||||
$this->assertSame(0, $color->blue);
|
||||
$this->assertSame(0, $color->alpha);
|
||||
}
|
||||
}
|
||||
34
src/PHPNative/Tailwind/tests/ComplexTest.php
Normal file
34
src/PHPNative/Tailwind/tests/ComplexTest.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
|
||||
use PHPNative\Renderer\Viewport;
|
||||
use PHPNative\Tailwind\Model\Size;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ComplexTest extends TestCase
|
||||
{
|
||||
public function testComplex(): void
|
||||
{
|
||||
$computed = StyleParser::parse("bg-lime-300 p-2 pb-3 m-2 ml-10 mr-10")->getValidStyles(MediaQueryEnum::normal, StateEnum::normal);
|
||||
$this->assertCount(3, $computed);
|
||||
$this->assertEquals(2, $computed[Padding::class]->left);
|
||||
$this->assertEquals(3, $computed[Padding::class]->bottom);
|
||||
$this->assertEquals(2, $computed[Margin::class]->top);
|
||||
$this->assertEquals(10, $computed[Margin::class]->left);
|
||||
$this->assertEquals(10, $computed[Margin::class]->right);
|
||||
}
|
||||
|
||||
public function testComplexWithModifier(): void
|
||||
{
|
||||
$computed = StyleParser::parse("bg-lime-300 hover:bg-red-200 md:hover:bg-green-100 md:m-50", 200);
|
||||
$this->assertCount(1, $computed->getValidStyles(MediaQueryEnum::ms, StateEnum::hover));
|
||||
$this->assertCount(2, $computed->getValidStyles(MediaQueryEnum::lx, StateEnum::active));
|
||||
$this->assertCount(2, $computed->getValidStyles(MediaQueryEnum::dm, StateEnum::active));
|
||||
}
|
||||
}
|
||||
95
src/PHPNative/Tailwind/tests/MarginTest.php
Normal file
95
src/PHPNative/Tailwind/tests/MarginTest.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
|
||||
use PHPNative\Tailwind\Parser\Margin;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @small
|
||||
*/
|
||||
class MarginTest extends TestCase
|
||||
{
|
||||
public function test_margin_overall(): void
|
||||
{
|
||||
$margin = Margin::parse("m-6");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(6, $margin->left);
|
||||
$this->assertSame(6, $margin->right);
|
||||
$this->assertSame(6, $margin->top);
|
||||
$this->assertSame(6, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_x(): void
|
||||
{
|
||||
$margin = Margin::parse("mx-2");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(2, $margin->left);
|
||||
$this->assertSame(2, $margin->right);
|
||||
$this->assertSame(null, $margin->top);
|
||||
$this->assertSame(null, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_y(): void
|
||||
{
|
||||
$margin = Margin::parse("my-2");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(null, $margin->left);
|
||||
$this->assertSame(null, $margin->right);
|
||||
$this->assertSame(2, $margin->top);
|
||||
$this->assertSame(2, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_t(): void
|
||||
{
|
||||
$margin = Margin::parse("mt-3");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(null, $margin->left);
|
||||
$this->assertSame(null, $margin->right);
|
||||
$this->assertSame(3, $margin->top);
|
||||
$this->assertSame(null, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_b(): void
|
||||
{
|
||||
$margin = Margin::parse("mb-3");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(null, $margin->left);
|
||||
$this->assertSame(null, $margin->right);
|
||||
$this->assertSame(null, $margin->top);
|
||||
$this->assertSame(3, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_l(): void
|
||||
{
|
||||
$margin = Margin::parse("ml-3");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(3, $margin->left);
|
||||
$this->assertSame(null, $margin->right);
|
||||
$this->assertSame(null, $margin->top);
|
||||
$this->assertSame(null, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_r(): void
|
||||
{
|
||||
$margin = Margin::parse("mr-3");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(null, $margin->left);
|
||||
$this->assertSame(3, $margin->right);
|
||||
$this->assertSame(null, $margin->top);
|
||||
$this->assertSame(null, $margin->bottom);
|
||||
}
|
||||
|
||||
public function test_margin_complex(): void
|
||||
{
|
||||
$margin = Margin::parse("m-2 ml-1 mr-3");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Margin::class, $margin);
|
||||
$this->assertSame(1, $margin->left);
|
||||
$this->assertSame(3, $margin->right);
|
||||
$this->assertSame(2, $margin->top);
|
||||
$this->assertSame(2, $margin->bottom);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
|
||||
use PHPNative\Tailwind\Parser\Padding;
|
||||
@ -7,83 +9,83 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PaddingTest extends TestCase
|
||||
{
|
||||
public function testPaddingOverall(): void
|
||||
public function test_padding_overall(): void
|
||||
{
|
||||
$padding = Padding::parse("p-6");
|
||||
self::assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
self::assertSame(6, $padding->left);
|
||||
self::assertSame(6, $padding->right);
|
||||
self::assertSame(6, $padding->top);
|
||||
self::assertSame(6, $padding->bottom);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(6, $padding->left);
|
||||
$this->assertSame(6, $padding->right);
|
||||
$this->assertSame(6, $padding->top);
|
||||
$this->assertSame(6, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingX(): void
|
||||
public function test_padding_x(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(2, $padding->left);
|
||||
$this->assertSame(2, $padding->right);
|
||||
$this->assertSame(null, $padding->top);
|
||||
$this->assertSame(null, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingY(): void
|
||||
public function test_padding_y(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(null, $padding->left);
|
||||
$this->assertSame(null, $padding->right);
|
||||
$this->assertSame(2, $padding->top);
|
||||
$this->assertSame(2, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingT(): void
|
||||
public function test_padding_t(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(null, $padding->left);
|
||||
$this->assertSame(null, $padding->right);
|
||||
$this->assertSame(3, $padding->top);
|
||||
$this->assertSame(null, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingB(): void
|
||||
public function test_padding_b(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(null, $padding->left);
|
||||
$this->assertSame(null, $padding->right);
|
||||
$this->assertSame(null, $padding->top);
|
||||
$this->assertSame(3, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingL(): void
|
||||
public function test_padding_l(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(3, $padding->left);
|
||||
$this->assertSame(null, $padding->right);
|
||||
$this->assertSame(null, $padding->top);
|
||||
$this->assertSame(null, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingR(): void
|
||||
public function test_padding_r(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(null, $padding->left);
|
||||
$this->assertSame(3, $padding->right);
|
||||
$this->assertSame(null, $padding->top);
|
||||
$this->assertSame(null, $padding->bottom);
|
||||
}
|
||||
|
||||
public function testPaddingComplex(): void
|
||||
public function test_padding_complex(): 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);
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Padding::class, $padding);
|
||||
$this->assertSame(1, $padding->left);
|
||||
$this->assertSame(3, $padding->right);
|
||||
$this->assertSame(2, $padding->top);
|
||||
$this->assertSame(2, $padding->bottom);
|
||||
}
|
||||
}
|
||||
44
src/PHPNative/Tailwind/tests/WidthTest.php
Normal file
44
src/PHPNative/Tailwind/tests/WidthTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Tests;
|
||||
|
||||
use PHPNative\Tailwind\Parser\Width;
|
||||
use PHPNative\Tailwind\Style\Unit;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class WidthTest extends TestCase
|
||||
{
|
||||
public function test_width_pixel(): void
|
||||
{
|
||||
$width = Width::parse("w-10");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Width::class, $width);
|
||||
$this->assertSame(10, $width->value);
|
||||
$this->assertSame(Unit::Pixel, $width->unit);
|
||||
}
|
||||
|
||||
public function test_width_percent_full(): void
|
||||
{
|
||||
$width = Width::parse("w-full");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Width::class, $width);
|
||||
$this->assertSame(100, $width->value);
|
||||
$this->assertSame(Unit::Percent, $width->unit);
|
||||
}
|
||||
|
||||
public function test_width_percent_one_half(): void
|
||||
{
|
||||
$width = Width::parse("w-1/2");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Width::class, $width);
|
||||
$this->assertSame(50, $width->value);
|
||||
$this->assertSame(Unit::Percent, $width->unit);
|
||||
}
|
||||
|
||||
public function test_width_percent_second_third(): void
|
||||
{
|
||||
$width = Width::parse("w-3/4");
|
||||
$this->assertInstanceOf(\PHPNative\Tailwind\Style\Width::class, $width);
|
||||
$this->assertSame(75, $width->value);
|
||||
$this->assertSame(Unit::Percent, $width->unit);
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\UI;
|
||||
|
||||
interface View
|
||||
{
|
||||
public function getWidget(): Widget;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\UI;
|
||||
|
||||
interface Widget
|
||||
{
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user