Skip to content

Commit f0ebce1

Browse files
committed
Merge branch 'release/0.8.8'
2 parents ef1695f + 56feb5b commit f0ebce1

6 files changed

Lines changed: 207 additions & 35 deletions

File tree

.version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"strategy": "semver",
33
"major": 0,
44
"minor": 8,
5-
"patch": 7,
5+
"patch": 8,
66
"build": 0
77
}

VERSIONLOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 0.8.8 2025-12-27
2+
* Added the ability to add multiple filters to routes.
3+
14
## 0.8.7 2025-12-11
25
* **Rate limit storage classes now use system abstractions** - FileRateLimitStorage and MemoryRateLimitStorage refactored to use `IClock` interface
36
* Added `neuron-php/core` 0.8.* dependency for system abstractions

src/Routing/RouteMap.php

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ class RouteMap
4242
public string $Path;
4343
public $Function;
4444
public array $Parameters;
45-
public string $Filter;
45+
public array $Filters;
4646
public string $Name;
4747
public array $Payload;
4848

4949
/**
5050
* RouteMap constructor.
5151
* @param $path string route path i.e. /part/new or /part/:id
5252
* @param $function callable the function to call on a matching route.
53-
* @param $filter string the name of the filter to match with this route.
53+
* @param $filters string|array the name(s) of the filter(s) to match with this route.
5454
* @throws Exception
5555
*/
5656

57-
public function __construct( string $path, callable $function, string $filter = '' )
57+
public function __construct( string $path, callable $function, string|array $filters = '' )
5858
{
5959
if( !is_callable( $function ) )
6060
{
@@ -64,7 +64,17 @@ public function __construct( string $path, callable $function, string $filter =
6464
$this->Path = $path;
6565
$this->Function = $function;
6666
$this->Parameters = [];
67-
$this->Filter = $filter;
67+
68+
// Support both string (legacy) and array (new) filter specification
69+
if( is_string( $filters ) )
70+
{
71+
$this->Filters = $filters === '' ? [] : [ $filters ];
72+
}
73+
else
74+
{
75+
$this->Filters = $filters;
76+
}
77+
6878
$this->Name = '';
6979
$this->Payload = [];
7080
}
@@ -124,21 +134,51 @@ public function setParameters( array $parameters ) : RouteMap
124134
}
125135

126136
/**
137+
* Get filters for this route
138+
* @return array
139+
*/
140+
public function getFilters(): array
141+
{
142+
return $this->Filters;
143+
}
144+
145+
/**
146+
* Set filters for this route
147+
* @param string|array $filters
148+
* @return RouteMap
149+
*/
150+
public function setFilters( string|array $filters ) : RouteMap
151+
{
152+
if( is_string( $filters ) )
153+
{
154+
$this->Filters = $filters === '' ? [] : [ $filters ];
155+
}
156+
else
157+
{
158+
$this->Filters = $filters;
159+
}
160+
return $this;
161+
}
162+
163+
/**
164+
* Legacy method for backward compatibility
165+
* @deprecated Use getFilters() instead
127166
* @return string|null
128167
*/
129-
public function getFilter()
168+
public function getFilter(): ?string
130169
{
131-
return $this->Filter;
170+
return count( $this->Filters ) > 0 ? $this->Filters[0] : null;
132171
}
133172

134173
/**
174+
* Legacy method for backward compatibility
175+
* @deprecated Use setFilters() instead
135176
* @param string $filter
136177
* @return RouteMap
137178
*/
138179
public function setFilter( string $filter ) : RouteMap
139180
{
140-
$this->Filter = $filter;
141-
return $this;
181+
return $this->setFilters( $filter );
142182
}
143183

144184
/**
@@ -236,23 +276,26 @@ protected function checkForDuplicateParams( $param, $params ): void
236276
*/
237277
public function execute( Router $router ): mixed
238278
{
239-
$filter = null;
279+
$filters = [];
240280

241-
if( $this->Filter )
281+
// Get all filter instances for this route
282+
foreach( $this->Filters as $filterName )
242283
{
243-
$filter = $router->getFilter( $this->Filter );
284+
$filters[] = $router->getFilter( $filterName );
244285
}
245286

246-
if( $filter )
287+
// Execute pre-filters in order
288+
foreach( $filters as $filter )
247289
{
248290
$filter->pre( $this );
249291
}
250292

293+
// Execute route function
251294
$function = $this->Function;
252-
253295
$result = $function( $this->Parameters );
254296

255-
if( $filter )
297+
// Execute post-filters in reverse order (LIFO)
298+
foreach( array_reverse( $filters ) as $filter )
256299
{
257300
$filter->post( $this );
258301
}

src/Routing/Router.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,13 @@ public function addFilter( string $filter ): void
107107
* @param array $routes
108108
* @param string $routeName
109109
* @param $function
110-
* @param $filter
110+
* @param $filters string|array
111111
* @return RouteMap
112112
* @throws \Exception
113113
*/
114-
protected function addRoute( array &$routes, string $routeName, $function, $filter ) : RouteMap
114+
protected function addRoute( array &$routes, string $routeName, $function, string|array $filters ) : RouteMap
115115
{
116-
$route = new RouteMap( $routeName, $function, $filter ?? '' );
116+
$route = new RouteMap( $routeName, $function, $filters ?? '' );
117117
$routes[] = $route;
118118

119119
return $route;
@@ -122,49 +122,49 @@ protected function addRoute( array &$routes, string $routeName, $function, $filt
122122
/**
123123
* @param string $route
124124
* @param $function
125-
* @param string|null $filter |null $filter
125+
* @param string|array|null $filters
126126
* @return RouteMap
127127
* @throws \Exception
128128
*/
129-
public function delete( string $route, $function, ?string $filter = null ) : RouteMap
129+
public function delete( string $route, $function, string|array|null $filters = null ) : RouteMap
130130
{
131-
return $this->addRoute( $this->_delete, $route, $function, $filter );
131+
return $this->addRoute( $this->_delete, $route, $function, $filters ?? '' );
132132
}
133133

134134
/**
135135
* @param string $route
136136
* @param $function
137-
* @param string|null $filter |null $filter
137+
* @param string|array|null $filters
138138
* @return RouteMap
139139
* @throws \Exception
140140
*/
141-
public function get( string $route, $function, ?string $filter = null ) : RouteMap
141+
public function get( string $route, $function, string|array|null $filters = null ) : RouteMap
142142
{
143-
return $this->addRoute( $this->_get, $route, $function, $filter );
143+
return $this->addRoute( $this->_get, $route, $function, $filters ?? '' );
144144
}
145145

146146
/**
147147
* @param string $route
148148
* @param $function
149-
* @param string|null $filter
149+
* @param string|array|null $filters
150150
* @return RouteMap
151151
* @throws \Exception
152152
*/
153-
public function post( string $route, $function, ?string $filter = null ) : RouteMap
153+
public function post( string $route, $function, string|array|null $filters = null ) : RouteMap
154154
{
155-
return $this->addRoute( $this->_post, $route, $function, $filter );
155+
return $this->addRoute( $this->_post, $route, $function, $filters ?? '' );
156156
}
157157

158158
/**
159159
* @param string $route
160160
* @param $function
161-
* @param string|null $filter |null $filter
161+
* @param string|array|null $filters
162162
* @return RouteMap
163163
* @throws \Exception
164164
*/
165-
public function put( string $route, $function, ?string $filter = null ) : RouteMap
165+
public function put( string $route, $function, string|array|null $filters = null ) : RouteMap
166166
{
167-
return $this->addRoute( $this->_put, $route, $function, $filter );
167+
return $this->addRoute( $this->_put, $route, $function, $filters ?? '' );
168168
}
169169

170170
/**

tests/unit/FilterTest.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,106 @@ function(){}
141141

142142
$this->assertTrue( $Filter );
143143
}
144+
145+
public function testMultipleFilters()
146+
{
147+
$executionOrder = [];
148+
149+
$this->Router->registerFilter(
150+
'FilterOne',
151+
new Routing\Filter(
152+
function() use ( &$executionOrder ) { $executionOrder[] = 'one-pre'; }
153+
)
154+
);
155+
156+
$this->Router->registerFilter(
157+
'FilterTwo',
158+
new Routing\Filter(
159+
function() use ( &$executionOrder ) { $executionOrder[] = 'two-pre'; }
160+
)
161+
);
162+
163+
$this->Router->get(
164+
'/test',
165+
function() use ( &$executionOrder ) { $executionOrder[] = 'route'; },
166+
[ 'FilterOne', 'FilterTwo' ]
167+
);
168+
169+
$Route = $this->Router->getRoute(
170+
Routing\RequestMethod::GET,
171+
'/test'
172+
);
173+
174+
$this->Router->dispatch( $Route );
175+
176+
$this->assertEquals( [ 'one-pre', 'two-pre', 'route' ], $executionOrder );
177+
}
178+
179+
public function testMultipleFiltersWithPost()
180+
{
181+
$executionOrder = [];
182+
183+
$this->Router->registerFilter(
184+
'FilterOne',
185+
new Routing\Filter(
186+
function() use ( &$executionOrder ) { $executionOrder[] = 'one-pre'; },
187+
function() use ( &$executionOrder ) { $executionOrder[] = 'one-post'; }
188+
)
189+
);
190+
191+
$this->Router->registerFilter(
192+
'FilterTwo',
193+
new Routing\Filter(
194+
function() use ( &$executionOrder ) { $executionOrder[] = 'two-pre'; },
195+
function() use ( &$executionOrder ) { $executionOrder[] = 'two-post'; }
196+
)
197+
);
198+
199+
$this->Router->get(
200+
'/test',
201+
function() use ( &$executionOrder ) { $executionOrder[] = 'route'; return 'result'; },
202+
[ 'FilterOne', 'FilterTwo' ]
203+
);
204+
205+
$Route = $this->Router->getRoute(
206+
Routing\RequestMethod::GET,
207+
'/test'
208+
);
209+
210+
$this->Router->dispatch( $Route );
211+
212+
// Pre-filters execute in order, route executes, post-filters execute in reverse (LIFO)
213+
$this->assertEquals(
214+
[ 'one-pre', 'two-pre', 'route', 'two-post', 'one-post' ],
215+
$executionOrder
216+
);
217+
}
218+
219+
public function testBackwardCompatibilityWithStringFilter()
220+
{
221+
$Filter = false;
222+
223+
$this->Router->registerFilter(
224+
'TestFilter',
225+
new Routing\Filter(
226+
function() use ( &$Filter ) { $Filter = true; }
227+
)
228+
);
229+
230+
// Test that string filter still works (backward compatibility)
231+
$this->Router->get(
232+
'/test',
233+
function(){},
234+
'TestFilter'
235+
);
236+
237+
$Route = $this->Router->getRoute(
238+
Routing\RequestMethod::GET,
239+
'/test'
240+
);
241+
242+
$this->Router->dispatch( $Route );
243+
244+
$this->assertTrue( $Filter );
245+
}
144246
}

tests/unit/RouteMapTest.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function testSetFilter()
9191
$result = $route->setFilter( 'rate-limit' );
9292

9393
$this->assertSame( $route, $result );
94-
$this->assertEquals( 'rate-limit', $route->Filter );
94+
$this->assertEquals( [ 'rate-limit' ], $route->Filters );
9595
}
9696

9797
public function testGetName()
@@ -219,15 +219,39 @@ public function testFilterInitialization()
219219
{
220220
$route = new Routing\RouteMap( '/test', function() {} );
221221

222-
// Filter should be empty string when not provided
223-
$this->assertEquals( '', $route->Filter );
222+
// Filters should be empty array when not provided
223+
$this->assertEquals( [], $route->Filters );
224224
}
225225

226226
public function testFilterInitializationWithValue()
227227
{
228228
$route = new Routing\RouteMap( '/test', function() {}, 'auth' );
229229

230-
// Filter should be set to provided value
231-
$this->assertEquals( 'auth', $route->Filter );
230+
// Filters should be array with provided value
231+
$this->assertEquals( [ 'auth' ], $route->Filters );
232+
}
233+
234+
public function testGetFilters()
235+
{
236+
$route = new Routing\RouteMap( '/test', function() {}, [ 'auth', 'csrf' ] );
237+
$this->assertEquals( [ 'auth', 'csrf' ], $route->getFilters() );
238+
}
239+
240+
public function testSetFilters()
241+
{
242+
$route = new Routing\RouteMap( '/test', function() {} );
243+
$result = $route->setFilters( [ 'auth', 'rate-limit' ] );
244+
245+
$this->assertSame( $route, $result );
246+
$this->assertEquals( [ 'auth', 'rate-limit' ], $route->Filters );
247+
}
248+
249+
public function testSetFiltersWithString()
250+
{
251+
$route = new Routing\RouteMap( '/test', function() {} );
252+
$result = $route->setFilters( 'csrf' );
253+
254+
$this->assertSame( $route, $result );
255+
$this->assertEquals( [ 'csrf' ], $route->Filters );
232256
}
233257
}

0 commit comments

Comments
 (0)