Skip to content

Commit c43395e

Browse files
committed
Merge branch 'main' into map-upload-files-into-request-properties
2 parents 8d0d689 + f3054f9 commit c43395e

File tree

8 files changed

+90
-34
lines changed

8 files changed

+90
-34
lines changed

src/Tempest/CommandBus/src/CommandBusConfig.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ public function __construct(
1717
) {
1818
}
1919

20+
/**
21+
* @throws CommandHandlerAlreadyExists
22+
*/
2023
public function addHandler(CommandHandler $commandHandler, string $commandName, MethodReflector $handler): self
2124
{
25+
if (array_key_exists($commandName, $this->handlers)) {
26+
throw new CommandHandlerAlreadyExists($commandName, new: $handler, existing: $this->handlers[$commandName]->handler);
27+
}
28+
2229
$this->handlers[$commandName] = $commandHandler
2330
->setCommandName($commandName)
2431
->setHandler($handler);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\CommandBus;
6+
7+
use Exception;
8+
use Tempest\Reflection\MethodReflector;
9+
10+
final class CommandHandlerAlreadyExists extends Exception
11+
{
12+
public function __construct(string $commandName, MethodReflector $new, MethodReflector $existing)
13+
{
14+
parent::__construct("Cannot add handler {$new->getShortName()}, {$existing->getShortName()} already handles {$commandName}.");
15+
}
16+
}

src/Tempest/CommandBus/tests/Fixtures/CreateUserCommandHandler.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ public function __invoke(CreateUserCommand $command): void
1818
$this->firstName = $command->firstName;
1919
$this->lastName = $command->lastName;
2020
}
21+
22+
#[CommandHandler]
23+
public function double(CreateUserCommand $command): void
24+
{
25+
// throws a CommandHandlerAlreadyExists exception since the command is already being handled by the __invoke method
26+
}
2127
}

src/Tempest/CommandBus/tests/GenericCommandBusTest.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Tempest\CommandBus\CommandBus;
99
use Tempest\CommandBus\CommandBusConfig;
1010
use Tempest\CommandBus\CommandHandler;
11+
use Tempest\CommandBus\CommandHandlerAlreadyExists;
1112
use Tempest\CommandBus\CommandHandlerNotFound;
1213
use Tempest\CommandBus\GenericCommandBus;
1314
use Tempest\CommandBus\Tests\Fixtures\CreateUserCommand;
@@ -21,6 +22,8 @@
2122
*/
2223
final class GenericCommandBusTest extends TestCase
2324
{
25+
private CommandBusConfig $config;
26+
2427
private CommandBus $commandBus;
2528

2629
public function test_getting_command_handler_that_exists(): void
@@ -44,26 +47,41 @@ public function test_exception_is_thrown_when_command_handler_doesnt_exist(): vo
4447
$this->commandBus->dispatch($command);
4548
}
4649

50+
public function test_exception_is_thrown_when_command_handler_already_exist(): void
51+
{
52+
$createUserCommandHandlerClass = new ClassReflector(CreateUserCommandHandler::class);
53+
$createUserCommandHandlerMethod = $createUserCommandHandlerClass->getMethod('__invoke');
54+
$createUserCommandHandler = $createUserCommandHandlerMethod->getAttribute(CommandHandler::class);
55+
56+
$this->expectException(CommandHandlerAlreadyExists::class);
57+
58+
$this->config->addHandler(
59+
commandHandler: $createUserCommandHandler,
60+
commandName: CreateUserCommand::class,
61+
handler: $createUserCommandHandlerClass->getMethod('double'),
62+
);
63+
}
64+
4765
protected function setUp(): void
4866
{
4967
parent::setUp();
5068

5169
// TODO: I'd like to make this easier to setup.
52-
$config = new CommandBusConfig();
70+
$this->config = new CommandBusConfig();
5371

5472
$createUserCommandHandlerClass = new ClassReflector(CreateUserCommandHandler::class);
5573
$createUserCommandHandlerMethod = $createUserCommandHandlerClass->getMethod('__invoke');
5674
$createUserCommandHandler = $createUserCommandHandlerMethod->getAttribute(CommandHandler::class);
5775

58-
$config->addHandler(
76+
$this->config->addHandler(
5977
commandHandler: $createUserCommandHandler,
6078
commandName: CreateUserCommand::class,
61-
handler: $createUserCommandHandlerMethod
79+
handler: $createUserCommandHandlerMethod,
6280
);
6381

6482
$this->commandBus = new GenericCommandBus(
6583
container: new GenericContainer(),
66-
commandBusConfig: $config
84+
commandBusConfig: $this->config,
6785
);
6886
}
6987
}

src/Tempest/Http/src/Commands/ServeCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
)]
1515
public function __invoke(string $host = 'localhost', int $port = 8000, string $publicDir = 'public/'): void
1616
{
17-
passthru("php -S {$host}:{$port} -t {$publicDir}");
17+
putenv("TEMPEST_PUBLIC_DIR={$publicDir}");
18+
$routerFile = __DIR__ . '/router.php';
19+
passthru("php -S {$host}:{$port} -t {$publicDir} {$routerFile}");
1820
}
1921
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$publicPath = getcwd() . '/' . rtrim($_ENV['TEMPEST_PUBLIC_DIR'], '/');
6+
7+
if (file_exists($publicPath . $_SERVER['REQUEST_URI'])) {
8+
return false;
9+
}
10+
11+
require_once $publicPath . '/index.php';

src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ final class RouteTreeNode
1717
/** @var array<string, RouteTreeNode> */
1818
private array $dynamicPaths = [];
1919

20-
private ?MarkedRoute $leaf = null;
20+
private ?MarkedRoute $targetRoute = null;
2121

2222
private function __construct(
2323
public readonly RouteTreeNodeType $type,
@@ -40,34 +40,26 @@ public static function createStaticRouteNode(string $name): self
4040
return new self(RouteTreeNodeType::Static, $name);
4141
}
4242

43-
public function addPath(array $pathSegments, MarkedRoute $markedRoute): void
43+
public function findOrCreateNodeForSegment(string $routeSegment): self
4444
{
45-
// If path segments is empty this node should target to given marked route
46-
if ($pathSegments === []) {
47-
if ($this->leaf !== null) {
48-
throw new DuplicateRouteException($markedRoute->route);
49-
}
50-
51-
$this->leaf = $markedRoute;
45+
// Translates a path segment like {id} into it's matching regex. Static segments remain the same
46+
$regexRouteSegment = self::convertDynamicSegmentToRegex($routeSegment);
5247

53-
return;
48+
// Returns a static or dynamic child node depending on the segment is dynamic or static
49+
if ($routeSegment === $regexRouteSegment) {
50+
return $this->staticPaths[$regexRouteSegment] ??= self::createStaticRouteNode($routeSegment);
5451
}
5552

56-
// Removes the first element of the pathSegments and use it to determin the next routing node
57-
$currentPathSegment = array_shift($pathSegments);
58-
59-
// Translates a path segment like {id} into it's matching regex. Static segments remain the same
60-
$regexPathSegment = self::convertDynamicSegmentToRegex($currentPathSegment);
53+
return $this->dynamicPaths[$regexRouteSegment] ??= self::createDynamicRouteNode($regexRouteSegment);
54+
}
6155

62-
// Find or create the next node to recurse into
63-
if ($currentPathSegment !== $regexPathSegment) {
64-
$node = $this->dynamicPaths[$regexPathSegment] ??= self::createDynamicRouteNode($regexPathSegment);
65-
} else {
66-
$node = $this->staticPaths[$regexPathSegment] ??= self::createStaticRouteNode($currentPathSegment);
56+
public function setTargetRoute(MarkedRoute $markedRoute): void
57+
{
58+
if ($this->targetRoute !== null) {
59+
throw new DuplicateRouteException($markedRoute->route);
6760
}
6861

69-
// Recurse into the newly created node to add the remainder of the path segments
70-
$node->addPath($pathSegments, $markedRoute);
62+
$this->targetRoute = $markedRoute;
7163
}
7264

7365
private static function convertDynamicSegmentToRegex(string $uriPart): string
@@ -107,14 +99,14 @@ public function toRegex(): string
10799
// Add a leaf alteration with an optional slash and end of line match `$`.
108100
// The `(*MARK:x)` is a marker which when this regex is matched will cause the matches array to contain
109101
// a key `"MARK"` with value `"x"`, it is used to track which route has been matched
110-
if ($this->leaf !== null) {
111-
$regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')';
102+
if ($this->targetRoute !== null) {
103+
$regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')';
112104
}
113105

114106
$regexp .= ")";
115-
} elseif ($this->leaf !== null) {
107+
} elseif ($this->targetRoute !== null) {
116108
// Add a singular leaf regex without alteration
117-
$regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')';
109+
$regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')';
118110
}
119111

120112
return $regexp;

src/Tempest/Http/src/Routing/Construction/RoutingTree.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ public function add(MarkedRoute $markedRoute): void
2222
$method = $markedRoute->route->method;
2323

2424
// Find the root tree node based on HTTP method
25-
$root = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute();
25+
$node = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute();
2626

27-
// Add path to tree using recursion
28-
$root->addPath($markedRoute->route->split(), $markedRoute);
27+
// Traverse the tree and find the node for each route segment
28+
foreach ($markedRoute->route->split() as $routeSegment) {
29+
$node = $node->findOrCreateNodeForSegment($routeSegment);
30+
}
31+
32+
$node->setTargetRoute($markedRoute);
2933
}
3034

3135
/** @return array<string, string> */

0 commit comments

Comments
 (0)