Skip to content

Commit ec7dfe4

Browse files
committed
tidy: refactor complexity
1 parent 2065516 commit ec7dfe4

7 files changed

Lines changed: 226 additions & 234 deletions

File tree

src/Assembly.php

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
<?php
22
namespace Gt\Routing;
33

4+
use ArrayIterator;
45
use Countable;
6+
use IteratorAggregate;
57
use Gt\Routing\Path\FileMatch\MagicFileMatch;
6-
use Iterator;
78

8-
/** @implements Iterator<int, string> */
9-
class Assembly implements Iterator, Countable {
9+
/** @implements IteratorAggregate<int, string> */
10+
class Assembly implements IteratorAggregate, Countable {
1011
const TYPE_LOGIC = "logic";
1112
const TYPE_VIEW = "view";
1213

1314
/** @var string[] Ordered list of file paths to load. */
1415
private array $pathList;
15-
private int $iteratorIndex;
1616

1717
public function __construct() {
1818
$this->pathList = [];
19-
$this->iteratorIndex = 0;
2019
}
2120

2221
public function add(string $path):void {
@@ -38,35 +37,19 @@ public function containsDistinctFile():bool {
3837
foreach($this->pathList as $path) {
3938
$fileName = pathinfo($path, PATHINFO_FILENAME);
4039
if(!str_starts_with($fileName, "_")
41-
|| !in_array($fileName, MagicFileMatch::MAGIC_FILENAME_ARRAY)) {
40+
|| !in_array($fileName, MagicFileMatch::MAGIC_FILENAME_ARRAY)) {
4241
return true;
4342
}
4443
}
45-
4644
return false;
4745
}
4846

49-
public function current():string {
50-
return $this->pathList[$this->iteratorIndex];
51-
}
52-
53-
public function next():void {
54-
$this->iteratorIndex++;
55-
}
56-
57-
public function key():int {
58-
return $this->iteratorIndex;
59-
}
60-
61-
public function valid():bool {
62-
return isset($this->pathList[$this->iteratorIndex]);
63-
}
64-
65-
public function rewind():void {
66-
$this->iteratorIndex = 0;
47+
/** @return ArrayIterator<int, string> */
48+
public function getIterator(): ArrayIterator {
49+
return new ArrayIterator($this->pathList);
6750
}
6851

69-
public function count():int {
52+
public function count(): int {
7053
return count($this->pathList);
7154
}
7255
}

src/BaseRouter.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,11 @@
33

44
use Gt\Config\ConfigSection;
55
use Gt\Http\ResponseStatusException\ClientError\HttpNotAcceptable;
6+
use Gt\Routing\Redirect\RedirectExceptionFactory;
67
use Gt\ServiceContainer\Container;
78
use Gt\ServiceContainer\Injector;
89
use Negotiation\Accept;
910
use Psr\Http\Message\RequestInterface;
10-
use Gt\Http\ResponseStatusException\Redirection\HttpFound;
11-
use Gt\Http\ResponseStatusException\Redirection\HttpMovedPermanently;
12-
use Gt\Http\ResponseStatusException\Redirection\HttpMultipleChoices;
13-
use Gt\Http\ResponseStatusException\Redirection\HttpNotModified;
14-
use Gt\Http\ResponseStatusException\Redirection\HttpPermanentRedirect;
15-
use Gt\Http\ResponseStatusException\Redirection\HttpSeeOther;
16-
use Gt\Http\ResponseStatusException\Redirection\HttpTemporaryRedirect;
1711
use ReflectionClass;
1812

1913
abstract class BaseRouter {
@@ -23,14 +17,17 @@ abstract class BaseRouter {
2317
private Injector $injector;
2418
private string $viewClassName;
2519
private bool $routeCompleted;
20+
private RedirectExceptionFactory $redirectExceptionFactory;
2621

2722
public function __construct(
2823
protected ?ConfigSection $routerConfig = null,
2924
?Assembly $viewAssembly = null,
3025
?Assembly $logicAssembly = null,
26+
?RedirectExceptionFactory $redirectExceptionFactory = null,
3127
) {
3228
$this->viewAssembly = $viewAssembly ?? new Assembly();
3329
$this->logicAssembly = $logicAssembly ?? new Assembly();
30+
$this->redirectExceptionFactory = $redirectExceptionFactory ?? new RedirectExceptionFactory();
3431
$this->routeCompleted = false;
3532
}
3633

@@ -47,22 +44,11 @@ public function handleRedirects(
4744
RequestInterface $request
4845
):void {
4946
$responseCode = $this->routerConfig?->getInt("redirect_response_code");
50-
51-
$responseClass = match($responseCode) {
52-
300 => HttpMultipleChoices::class,
53-
301 => HttpMovedPermanently::class,
54-
302 => HttpFound::class,
55-
303 => HttpSeeOther::class,
56-
304 => HttpNotModified::class,
57-
307 => HttpTemporaryRedirect::class,
58-
default => HttpPermanentRedirect::class
59-
};
60-
6147
$uri = $request->getUri()->getPath();
6248

6349
foreach($redirects as $old => $new) {
6450
if($old === $uri) {
65-
throw new $responseClass($new);
51+
throw $this->redirectExceptionFactory->create($responseCode, $new);
6652
}
6753
}
6854
}

src/LogicStream/LogicStreamWrapper.php

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -57,56 +57,101 @@ public function stream_stat():array {
5757
* This function checks for a namespace declaration at the top of the
5858
* script, and if there is no declaration, it will inject one that
5959
* matches the current path.
60+
*
61+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
6062
*/
6163
private function loadContents(SplFileObject $file):void {
64+
$this->processFileUntilNamespace($file);
65+
$this->appendRemainingContents($file);
66+
}
67+
68+
/**
69+
* Process the file until a namespace is found or added
70+
*/
71+
private function processFileUntilNamespace(SplFileObject $file): void {
6272
$foundNamespace = false;
6373
$withinBlockComment = false;
6474
$lineNumber = 0;
6575

6676
while(!$file->eof() && !$foundNamespace) {
6777
$line = $file->fgets();
78+
6879
if($lineNumber === 0) {
69-
// TODO: Allow hashbangs before <?php
70-
// Maybe this is possible by just skipping while the first character is a hash,
71-
// and not increasing the line number.
72-
if(!str_starts_with($line, "<?php")) {
73-
throw new Exception(
74-
"Logic file at "
75-
. $this->path
76-
. " must start by opening a PHP tag. "
77-
. "See https://www.php.gt/routing/logic-stream-wrapper"
78-
);
79-
}
80+
$this->validateFirstLine($line);
8081
}
81-
$trimmedLine = trim($line);
8282

83-
if(str_starts_with($trimmedLine, "/*")) {
84-
$withinBlockComment = true;
85-
}
83+
$trimmedLine = trim($line);
84+
$withinBlockComment = $this->handleBlockComment($trimmedLine, $withinBlockComment);
8685

87-
if($withinBlockComment) {
88-
if(str_contains($trimmedLine, "*/")) {
89-
$withinBlockComment = false;
90-
}
91-
}
92-
elseif($lineNumber > 0) {
93-
if(str_starts_with($trimmedLine, "namespace")) {
94-
$foundNamespace = true;
95-
}
96-
elseif($trimmedLine ) {
97-
$namespace = new LogicStreamNamespace(
98-
$this->path,
99-
self::NAMESPACE_PREFIX
100-
);
101-
$this->contents .= "namespace $namespace;\t";
102-
$foundNamespace = true;
103-
}
86+
if(!$withinBlockComment && $lineNumber > 0) {
87+
$foundNamespace = $this->processNamespace($trimmedLine);
10488
}
10589

10690
$this->contents .= $line;
10791
$lineNumber++;
10892
}
93+
}
10994

95+
/**
96+
* Validate that the first line of the file starts with <?php
97+
*/
98+
private function validateFirstLine(string $line): void {
99+
if(!str_starts_with($line, "<?php")) {
100+
throw new Exception(
101+
"Logic file at "
102+
. $this->path
103+
. " must start by opening a PHP tag. "
104+
. "See https://www.php.gt/routing/logic-stream-wrapper"
105+
);
106+
}
107+
}
108+
109+
/**
110+
* Handle block comments in the file
111+
*
112+
* @param string $trimmedLine The trimmed line to check
113+
* @param bool $withinBlockComment Whether we're currently within a block comment
114+
* @return bool Updated block comment status
115+
*/
116+
private function handleBlockComment(string $trimmedLine, bool $withinBlockComment): bool {
117+
if(str_starts_with($trimmedLine, "/*")) {
118+
$withinBlockComment = true;
119+
}
120+
121+
if($withinBlockComment && str_contains($trimmedLine, "*/")) {
122+
$withinBlockComment = false;
123+
}
124+
125+
return $withinBlockComment;
126+
}
127+
128+
/**
129+
* Process namespace declarations
130+
*
131+
* @param string $trimmedLine The trimmed line to check
132+
* @return bool Whether a namespace was found or added
133+
*/
134+
private function processNamespace(string $trimmedLine): bool {
135+
if(str_starts_with($trimmedLine, "namespace")) {
136+
return true;
137+
}
138+
139+
if($trimmedLine) {
140+
$namespace = new LogicStreamNamespace(
141+
$this->path,
142+
self::NAMESPACE_PREFIX
143+
);
144+
$this->contents .= "namespace $namespace;\t";
145+
return true;
146+
}
147+
148+
return false;
149+
}
150+
151+
/**
152+
* Append the remaining contents of the file
153+
*/
154+
private function appendRemainingContents(SplFileObject $file): void {
110155
while(!$file->eof()) {
111156
$line = $file->fgets();
112157
$this->contents .= $line;

src/Path/DynamicPath.php

Lines changed: 87 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,100 @@ public function get(?string $key = null, ?bool $extra = false):?string {
2020
foreach($this->assemblyList as $assembly) {
2121
foreach($assembly as $filePath) {
2222
$filePathParts = explode("/", $filePath);
23-
foreach($filePathParts as $i => $filePart) {
24-
$filePart = strtok($filePart, ".");
25-
if($filePart[0] !== "@") {
26-
continue;
27-
}
28-
29-
if(is_null($key)) {
30-
if($extra) {
31-
$test = "";
32-
for($ppi = count($filePathParts), $len = count($requestPathParts);
33-
$ppi < $len; $ppi++) {
34-
$test .= $requestPathParts[$ppi];
35-
$test .= "/";
36-
}
37-
return rtrim($test, "/");
38-
}
39-
else {
40-
return $requestPathParts[count($filePathParts) - 1] ?? null;
41-
}
42-
}
43-
44-
if(ltrim($filePart, "@") !== $key) {
45-
continue;
46-
}
47-
48-
$requestPart = $requestPathParts[$i] ?? null;
49-
if(!$requestPart) {
50-
continue;
51-
}
52-
53-
return $requestPart;
23+
$result = $this->processFilePathParts($filePathParts, $requestPathParts, $key, $extra);
24+
if($result !== null) {
25+
return $result;
5426
}
5527
}
5628
}
5729

5830
return null;
5931
}
6032

33+
/**
34+
* Process file path parts to find matching parameters
35+
*
36+
* @param array<string> $filePathParts Parts of the file path
37+
* @param array<string> $requestPathParts Parts of the request path
38+
* @param string|null $key The key to look for
39+
* @param bool|null $extra Whether to include extra path parts
40+
* @return string|null The matched parameter or null if no match
41+
*/
42+
private function processFilePathParts(array $filePathParts, array $requestPathParts, ?string $key, ?bool $extra): ?string {
43+
foreach($filePathParts as $i => $filePart) {
44+
$filePart = strtok($filePart, ".");
45+
if($filePart[0] !== "@") {
46+
continue;
47+
}
48+
49+
if(is_null($key)) {
50+
return $this->handleNullKey($filePathParts, $requestPathParts, $extra);
51+
}
52+
53+
$result = $this->handleNamedKey($filePart, $requestPathParts, $i, $key);
54+
if($result !== null) {
55+
return $result;
56+
}
57+
}
58+
59+
return null;
60+
}
61+
62+
/**
63+
* Handle the case when the key is null
64+
*
65+
* @param array<string> $filePathParts Parts of the file path
66+
* @param array<string> $requestPathParts Parts of the request path
67+
* @param bool|null $extra Whether to include extra path parts
68+
* @return string|null The matched parameter
69+
*/
70+
private function handleNullKey(array $filePathParts, array $requestPathParts, ?bool $extra): ?string {
71+
if($extra) {
72+
return $this->getExtraPathParts($filePathParts, $requestPathParts);
73+
}
74+
75+
return $requestPathParts[count($filePathParts) - 1] ?? null;
76+
}
77+
78+
/**
79+
* Get extra path parts beyond the file path
80+
*
81+
* @param array<string> $filePathParts Parts of the file path
82+
* @param array<string> $requestPathParts Parts of the request path
83+
* @return string The extra path parts
84+
*/
85+
private function getExtraPathParts(array $filePathParts, array $requestPathParts): string {
86+
$test = "";
87+
for($ppi = count($filePathParts), $len = count($requestPathParts);
88+
$ppi < $len; $ppi++) {
89+
$test .= $requestPathParts[$ppi];
90+
$test .= "/";
91+
}
92+
return rtrim($test, "/");
93+
}
94+
95+
/**
96+
* Handle the case when a specific key is provided
97+
*
98+
* @param string $filePart The current file path part
99+
* @param array<string> $requestPathParts Parts of the request path
100+
* @param int $i The current index
101+
* @param string $key The key to look for
102+
* @return string|null The matched parameter or null if no match
103+
*/
104+
private function handleNamedKey(string $filePart, array $requestPathParts, int $i, string $key): ?string {
105+
if(ltrim($filePart, "@") !== $key) {
106+
return null;
107+
}
108+
109+
$requestPart = $requestPathParts[$i] ?? null;
110+
if(!$requestPart) {
111+
return null;
112+
}
113+
114+
return $requestPart;
115+
}
116+
61117
public function getUrl(string $viewBasePath):string {
62118
$path = "";
63119

0 commit comments

Comments
 (0)