diff options
| author | Sam Light <sam@lightscale.co.uk> | 2026-06-10 19:00:32 +0100 |
|---|---|---|
| committer | Sam Light <sam@lightscale.co.uk> | 2026-06-10 19:00:32 +0100 |
| commit | 6ad39e3ba64e518bcdba1fc32b6247375be39a8a (patch) | |
| tree | cc7ba7901043428b327d454580460e7b67c399b9 | |
| parent | 9e6a18235564df5046c40570a864bcbd5c8bcf62 (diff) | |
shared code in traits for common features for groups
| -rw-r--r-- | src/Concerns/CreatesGroups.php | 28 | ||||
| -rw-r--r-- | src/Concerns/CreatesRoutes.php | 41 | ||||
| -rw-r--r-- | src/Concerns/HasAncestors.php | 37 | ||||
| -rw-r--r-- | src/Group.php | 65 | ||||
| -rw-r--r-- | src/GroupDefinition.php | 22 | ||||
| -rw-r--r-- | src/PathSegment.php | 29 | ||||
| -rw-r--r-- | src/Router.php | 43 | ||||
| -rw-r--r-- | tests/Unit/GroupDefinitionTest.php | 15 | ||||
| -rw-r--r-- | tests/Unit/GroupTest.php | 81 |
9 files changed, 301 insertions, 60 deletions
diff --git a/src/Concerns/CreatesGroups.php b/src/Concerns/CreatesGroups.php new file mode 100644 index 0000000..e6e4588 --- /dev/null +++ b/src/Concerns/CreatesGroups.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Lightscale\Router\Concerns; + +use Lightscale\Router\Group; + +trait CreatesGroups +{ + public function prefix(string $value): Group + { + return new Group( + router: $this->getRouter(), + parent: $this->getGroup(), + prefix: $value, + ); + } + + public function name(string $value): Group + { + return new Group( + router: $this->getRouter(), + parent: $this->getGroup(), + name: $value, + ); + } +} diff --git a/src/Concerns/CreatesRoutes.php b/src/Concerns/CreatesRoutes.php new file mode 100644 index 0000000..6a8f65b --- /dev/null +++ b/src/Concerns/CreatesRoutes.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Lightscale\Router\Concerns; + +use Lightscale\Router\Enums\HttpMethod; +use Lightscale\Router\RouteDefinition; + +trait CreatesRoutes +{ + public function get(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Get, $path, $handler); + } + + public function post(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Post, $path, $handler); + } + + public function put(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Put, $path, $handler); + } + + public function patch(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Patch, $path, $handler); + } + + public function delete(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Delete, $path, $handler); + } + + public function any(string $path, callable $handler): RouteDefinition + { + return $this->make(HttpMethod::Any, $path, $handler); + } +} diff --git a/src/Concerns/HasAncestors.php b/src/Concerns/HasAncestors.php new file mode 100644 index 0000000..248e9c8 --- /dev/null +++ b/src/Concerns/HasAncestors.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace Lightscale\Router\Concerns; + +trait HasAncestors +{ + private const MAX_ANCESTORY_DEPTH = 100; + + /** @return self[] */ + public function getAncestors(): array + { + $results = []; + + $count = 0; + $instance = $this->parent; + while ( + null !== $instance + && $count++ < self::MAX_ANCESTORY_DEPTH + ) { + $results[] = $instance; + $instance = $instance->parent; + } + + return array_reverse($results); + } + + /** @return self[] */ + public function getAncestorsAndSelf(): array + { + $results = $this->getAncestors(); + $results[] = $this; + + return $results; + } +} diff --git a/src/Group.php b/src/Group.php new file mode 100644 index 0000000..4e6c71e --- /dev/null +++ b/src/Group.php @@ -0,0 +1,65 @@ +<?php + +declare(strict_types=1); + +namespace Lightscale\Router; + +class Group +{ + use Concerns\HasAncestors; + + public function __construct( + private Router $router, + private ?Group $parent, + private ?string $prefix = null, + private ?string $name = null, + ) { + } + + public function getRouter(): Router + { + return $this->router; + } + + public function getParent(): ?static + { + return $this->parent; + } + + public function prefix(string $value): static + { + $this->prefix = $value; + + return $this; + } + + public function getPrefix(): ?string + { + return $this->prefix; + } + + public function getPath(): string + { + + } + + public function name(string $value): static + { + $this->name = $value; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + /** @param callable(self): void $cb */ + public function group(callable $cb): void + { + ($cb)(new GroupDefinition( + $this + )); + } +} diff --git a/src/GroupDefinition.php b/src/GroupDefinition.php index 444ba91..41e359d 100644 --- a/src/GroupDefinition.php +++ b/src/GroupDefinition.php @@ -4,10 +4,30 @@ declare(strict_types=1); namespace Lightscale\Router; +use Lightscale\Router\Enums\HttpMethod; + class GroupDefinition { + use Concerns\CreatesGroups; + use Concerns\CreatesRoutes; + public function __construct( - private Router $router, + private Group $group, ) { } + + public function make(HttpMethod $method, string $path, callable $handler): RouteDefinition + { + + } + + private function getGroup(): Group + { + return $this->group; + } + + private function getRouter(): Router + { + return $this->group->getRouter(); + } } diff --git a/src/PathSegment.php b/src/PathSegment.php index 2fc4cbb..01f3fc7 100644 --- a/src/PathSegment.php +++ b/src/PathSegment.php @@ -10,7 +10,7 @@ use Lightscale\Router\Exceptions\MissingParameterException; class PathSegment { - private const MAX_ANCESTORY_DEPTH = 100; + use Concerns\HasAncestors; /** @var array<string, self> */ protected array $children; @@ -73,33 +73,6 @@ class PathSegment return $this->parent; } - /** @return self[] */ - public function getAncestors(): array - { - $results = []; - - $count = 0; - $instance = $this->parent; - while ( - null !== $instance - && $count++ < self::MAX_ANCESTORY_DEPTH - ) { - $results[] = $instance; - $instance = $instance->parent; - } - - return array_reverse($results); - } - - /** @return self[] */ - public function getAncestorsAndSelf(): array - { - $results = $this->getAncestors(); - $results[] = $this; - - return $results; - } - public function addChild(self $segment): void { $segment->setParent($this); diff --git a/src/Router.php b/src/Router.php index b219651..d6b255a 100644 --- a/src/Router.php +++ b/src/Router.php @@ -12,6 +12,9 @@ use Psr\Http\Message\ResponseInterface; class Router { + use Concerns\CreatesRoutes; + use Concerns\CreatesGroups; + private PathSegment $root; private Strategy $strategy; @@ -24,6 +27,16 @@ class Router $this->strategy = new BasicStrategy(); } + private function getRouter(): self + { + return $this; + } + + private function getGroup(): null + { + return null; + } + public function getStrategy(): Strategy { return $this->strategy; @@ -136,36 +149,6 @@ class Router return new RouteDefinition($this, $route); } - public function get(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Get, $path, $handler); - } - - public function post(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Post, $path, $handler); - } - - public function put(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Put, $path, $handler); - } - - public function patch(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Patch, $path, $handler); - } - - public function delete(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Delete, $path, $handler); - } - - public function any(string $path, callable $handler): RouteDefinition - { - return $this->make(HttpMethod::Any, $path, $handler); - } - public function addNamedRoute(string $name, Route $route): void { $this->namedRoutes[$name] = $route; diff --git a/tests/Unit/GroupDefinitionTest.php b/tests/Unit/GroupDefinitionTest.php index 3a064a1..59a8d3d 100644 --- a/tests/Unit/GroupDefinitionTest.php +++ b/tests/Unit/GroupDefinitionTest.php @@ -2,9 +2,22 @@ declare(strict_types=1); +use Lightscale\Router\Group; use Lightscale\Router\GroupDefinition; use Lightscale\Router\Router; +$make = fn () => new GroupDefinition(new Group(new Router(), null)); + it('initializes') - ->expect(fn () => new GroupDefinition(new Router())) + ->expect(fn () => $make()) ->toBeInstanceOf(GroupDefinition::class); + +it('creates group with prefix') + ->expect(fn () => $make()->prefix('/test')) + ->toBeInstanceOf(Group::class) + ->getPrefix()->toBe('/test'); + +it('creates group with name') + ->expect(fn () => $make()->name('name')) + ->toBeInstanceOf(Group::class) + ->getName()->toBe('name'); diff --git a/tests/Unit/GroupTest.php b/tests/Unit/GroupTest.php new file mode 100644 index 0000000..3b17f19 --- /dev/null +++ b/tests/Unit/GroupTest.php @@ -0,0 +1,81 @@ +<?php + +declare(strict_types=1); + +use Lightscale\Router\Group; +use Lightscale\Router\GroupDefinition; +use Lightscale\Router\Router; +use Lightscale\Router\Test\Utils\TestCallable; + +$make = fn(?Group $parent = null, ...$args) => new Group(new Router, $parent, ...$args); + +it('initializes with null defaults') + ->expect(fn() => $make()) + ->toBeInstanceOf(Group::class) + ->getName()->toBeNull() + ->getPrefix()->toBeNull(); + +it('initializes with values') + ->expect(new Group(new Router, null, '/test-path', 'test-name')) + ->toBeInstanceOf(Group::class) + ->getName()->toBe('test-name') + ->getPrefix()->toBe('/test-path'); + +it('can set/get prefix') + ->expect(fn() => $make()->prefix('/test')) + ->toBeInstanceOf(Group::class) + ->getPrefix()->toBe('/test'); + +it('can set/get name') + ->expect(fn() => $make()->name('test')) + ->toBeInstanceOf(Group::class) + ->getName()->toBe('test'); + +it('calls group call back with definition', function () use ($make) { + $cb = TestCallable::make(fn() => null); + $make()->group($cb); + + $cb->assertCalled(); + $call = $cb->getLastCall(); + + expect($call?->args)->{0}->toBeInstanceOf(GroupDefinition::class); +}); + +it('can have a parent') + ->expect(fn () => $make($make())->getParent()) + ->toBeInstanceOf(Group::class); + +it('can have a null parent') + ->expect(fn () => $make(null)->getParent()) + ->toBeNull(); + +it('can get all ancestors') + ->expect(fn () => $make($make($make()))->getAncestors()) + ->toBeArray() + ->toContainOnlyInstancesOf(Group::class) + ->toHaveCount(2); + +it('order all ancestors root first', function () use($make) { + $g3 = $make($g2 = $make($g1 = $make())); + + expect($g3->getAncestors()) + ->toHaveCount(2) + ->{0}->toBe($g1) + ->{1}->toBe($g2); +}); + +it('can get all ancestors and self') + ->expect(fn () => $make($make($make()))->getAncestorsAndSelf()) + ->toBeArray() + ->toContainOnlyInstancesOf(Group::class) + ->toHaveCount(3); + +it('order all ancestors and self root first', function () use($make) { + $g3 = $make($g2 = $make($g1 = $make())); + + expect($g3->getAncestorsAndSelf()) + ->toHaveCount(3) + ->{0}->toBe($g1) + ->{1}->toBe($g2) + ->{2}->toBe($g3); +}); |
