Skip to content

Commit 935d0cd

Browse files
committed
Merge branch 'release/0.7.2'
2 parents 78676d3 + 401bb89 commit 935d0cd

File tree

15 files changed

+1063
-19
lines changed

15 files changed

+1063
-19
lines changed

.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": 7,
5-
"patch": 1,
5+
"patch": 2,
66
"build": 0
77
}

examples/config/routes.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@ routes:
3535
route: /bad_request
3636
method: GET
3737
controller: Mvc\TestController@bad_request
38+
39+
404:
40+
route: /404
41+
method: GET
42+
controller: Neuron\Mvc\Controllers\HttpCodes@code404

readme.md

Lines changed: 161 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,157 @@ public function profile(Request $request): string
208208
}
209209
```
210210

211+
### URL Helpers
212+
213+
The framework provides Rails-style URL helpers for generating URLs from named routes. This makes it easy to generate consistent URLs throughout your application.
214+
215+
#### Route Naming
216+
217+
Routes are automatically named based on their configuration key in the YAML file:
218+
219+
```yaml
220+
routes:
221+
user_profile: # This becomes the route name
222+
route: /users/{id}
223+
method: GET
224+
controller: App\Controllers\UserController@profile
225+
226+
admin_user_posts:
227+
route: /admin/users/{user_id}/posts/{post_id}
228+
method: GET
229+
controller: App\Controllers\AdminController@userPosts
230+
```
231+
232+
#### Using URL Helpers in Controllers
233+
234+
Controllers can use URL helpers directly via magic methods:
235+
236+
```php
237+
class UserController extends Base
238+
{
239+
public function show($id): string
240+
{
241+
$user = User::find($id);
242+
243+
// Magic methods for URL generation
244+
$editUrl = $this->userEditPath(['id' => $id]);
245+
$absoluteUrl = $this->userProfileUrl(['id' => $id]);
246+
247+
// Use in redirects
248+
if (!$user) {
249+
return redirect($this->userIndexPath());
250+
}
251+
252+
return $this->renderHtml(HttpResponseStatus::OK, [
253+
'user' => $user,
254+
'edit_url' => $editUrl
255+
]);
256+
}
257+
258+
public function create(): string
259+
{
260+
// After creating user, redirect using magic method
261+
$user = new User($request->all());
262+
$user->save();
263+
264+
return redirect($this->userProfilePath(['id' => $user->id]));
265+
}
266+
}
267+
```
268+
269+
#### Direct URL Helper Methods
270+
271+
Controllers also provide direct helper methods:
272+
273+
```php
274+
// Generate relative URLs
275+
$profileUrl = $this->urlFor('user_profile', ['id' => 123]);
276+
277+
// Generate absolute URLs
278+
$absoluteUrl = $this->urlForAbsolute('user_profile', ['id' => 123]);
279+
280+
// Check if route exists
281+
if ($this->routeExists('user_profile')) {
282+
// Route is available
283+
}
284+
285+
// Get UrlHelper instance for advanced usage
286+
$urlHelper = $this->urlHelper();
287+
```
288+
289+
#### Using URL Helpers in Views
290+
291+
URL helpers are automatically available in all views through the injected `$urlHelper` variable:
292+
293+
```php
294+
<!-- resources/views/user/profile.php -->
295+
<div class="user-profile">
296+
<h1><?= $user->name ?></h1>
297+
298+
<!-- Magic methods in views -->
299+
<a href="<?= $urlHelper->userEditPath(['id' => $user->id]) ?>" class="btn">Edit</a>
300+
<a href="<?= $urlHelper->userPostsPath(['user_id' => $user->id]) ?>" class="btn">View Posts</a>
301+
302+
<!-- Complex routes work too -->
303+
<a href="<?= $urlHelper->adminUserReportsPath(['id' => $user->id, 'year' => date('Y')]) ?>">
304+
Admin Reports
305+
</a>
306+
307+
<!-- Direct method calls -->
308+
<a href="<?= $urlHelper->routePath('user_profile', ['id' => $user->id]) ?>">Profile</a>
309+
<a href="<?= $urlHelper->routeUrl('user_profile', ['id' => $user->id]) ?>">Share Link</a>
310+
</div>
311+
```
312+
313+
#### Magic Method Conventions
314+
315+
The magic methods follow Rails naming conventions:
316+
317+
| Route Name in YAML | Magic Method (Relative) | Magic Method (Absolute) | Generated URL |
318+
|---------------------|------------------------|-------------------------|---------------|
319+
| `user_profile` | `userProfilePath()` | `userProfileUrl()` | `/users/123` |
320+
| `user_edit` | `userEditPath()` | `userEditUrl()` | `/users/123/edit` |
321+
| `admin_user_posts` | `adminUserPostsPath()` | `adminUserPostsUrl()` | `/admin/users/1/posts/2` |
322+
| `blog_category` | `blogCategoryPath()` | `blogCategoryUrl()` | `/blog/category/tech` |
323+
324+
#### URL Helper Methods
325+
326+
| Method | Description | Example |
327+
|--------|-------------|---------|
328+
| `routePath($name, $params)` | Generate relative URL | `$urlHelper->routePath('user_profile', ['id' => 123])` |
329+
| `routeUrl($name, $params)` | Generate absolute URL | `$urlHelper->routeUrl('user_profile', ['id' => 123])` |
330+
| `routeExists($name)` | Check if route exists | `$urlHelper->routeExists('user_profile')` |
331+
| `getAvailableRoutes()` | List all named routes | `$urlHelper->getAvailableRoutes()` |
332+
| `{routeName}Path($params)` | Magic method for relative URL | `$urlHelper->userProfilePath(['id' => 123])` |
333+
| `{routeName}Url($params)` | Magic method for absolute URL | `$urlHelper->userProfileUrl(['id' => 123])` |
334+
335+
#### Error Handling
336+
337+
URL helpers gracefully handle missing routes:
338+
339+
```php
340+
// Returns null if route doesn't exist
341+
$url = $urlHelper->nonExistentRoutePath(['id' => 123]);
342+
343+
if ($url === null) {
344+
// Handle missing route
345+
$url = $urlHelper->userIndexPath(); // fallback
346+
}
347+
```
348+
349+
#### Advanced Usage
350+
351+
```php
352+
// Get all available routes for debugging
353+
$routes = $urlHelper->getAvailableRoutes();
354+
foreach ($routes as $route) {
355+
echo "Route: {$route['name']} -> {$route['method']} {$route['path']}\n";
356+
}
357+
358+
// Custom UrlHelper instance
359+
$customHelper = new UrlHelper($customRouter);
360+
```
361+
211362
## Configuration
212363

213364
All YAML config file parameters can be overridden by environment variables in the form of `<CATEGORY>_<KEY>`, e.g.
@@ -562,17 +713,18 @@ neuron mvc:routes:list --json
562713

563714
```
564715
MVC Routes
565-
================================================================================
566-
Pattern | Method | Controller | Action | Parameters
567-
--------------------------------------------------------------------------------
568-
/ | GET | HomeController | index | -
569-
/user/{id} | GET | UserController | profile | id
570-
/api/users | GET | Api\UserController | list | -
571-
/api/users | POST | Api\UserController | create | name, email
572-
/products | GET | ProductController | list | -
573-
/products/{id} | GET | ProductController | details | id
716+
======================================================================================
717+
Name | Pattern | Method | Controller | Action
718+
--------------------------------------------------------------------------------------
719+
home | / | GET | HomeController | index
720+
user_profile | /user/{id} | GET | UserController | profile
721+
api_users_list | /api/users | GET | Api\UserController | list
722+
api_users_create | /api/users | POST | Api\UserController | create
723+
products_list | /products | GET | ProductController | list
724+
product_details | /products/{id} | GET | ProductController | details
574725
575726
Total routes: 6
727+
Named routes: 6
576728
Methods: GET: 4, POST: 2
577729
```
578730

src/Mvc/Cache/ViewCache.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22
namespace Neuron\Mvc\Cache;
33

4+
use Exception;
45
use Neuron\Log\Log;
56
use Neuron\Mvc\Cache\Exceptions\CacheException;
67
use Neuron\Mvc\Cache\Storage\ICacheStorage;
@@ -180,9 +181,51 @@ public function gc(): int
180181
*/
181182
private function hashData( array $Data ): string
182183
{
183-
ksort( $Data );
184+
// Filter out non-serializable objects (like UrlHelper)
185+
$serializableData = $this->filterSerializableData( $Data );
184186

185-
return md5( serialize( $Data ) );
187+
ksort( $serializableData );
188+
189+
return md5( serialize( $serializableData ) );
190+
}
191+
192+
/**
193+
* Filter out non-serializable data from array
194+
*
195+
* @param array $Data
196+
* @return array
197+
*/
198+
private function filterSerializableData( array $Data ): array
199+
{
200+
$filtered = [];
201+
202+
foreach( $Data as $key => $value )
203+
{
204+
// Skip objects that are likely to contain non-serializable content
205+
if( is_object( $value ) )
206+
{
207+
$className = get_class( $value );
208+
// Skip UrlHelper and Router objects as they contain closures
209+
if( str_contains( $className, 'UrlHelper' ) || str_contains( $className, 'Router' ) )
210+
{
211+
continue;
212+
}
213+
214+
// Try to serialize other objects, skip if it fails
215+
try
216+
{
217+
serialize( $value );
218+
}
219+
catch( Exception $e )
220+
{
221+
continue;
222+
}
223+
}
224+
225+
$filtered[$key] = $value;
226+
}
227+
228+
return $filtered;
186229
}
187230

188231
/**

src/Mvc/Cli/Commands/Routes/ListCommand.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ private function parseRoute( string $routeName, $config ): ?array
221221
}
222222

223223
return [
224+
'name' => $routeName,
224225
'pattern' => $pattern,
225226
'controller' => $controller,
226227
'action' => $action,
@@ -276,18 +277,26 @@ private function outputTable( array $routes ): void
276277
$this->output->title( 'MVC Routes' );
277278

278279
// Prepare table headers
279-
$headers = ['Pattern', 'Method', 'Controller', 'Action', 'Parameters'];
280+
$headers = ['Name', 'Pattern', 'Method', 'Controller', 'Action'];
280281

281282
// Prepare table rows
282283
$rows = [];
284+
$namedRoutes = 0;
285+
283286
foreach( $routes as $route )
284287
{
288+
$routeName = $route['name'] ?? '';
289+
if( !empty( $routeName ) )
290+
{
291+
$namedRoutes++;
292+
}
293+
285294
$rows[] = [
295+
$routeName ?: '-',
286296
$route['pattern'],
287297
$route['method'],
288298
$route['controller'],
289-
$route['action'],
290-
empty( $route['parameters'] ) ? '-' : implode( ', ', $route['parameters'] )
299+
$route['action']
291300
];
292301
}
293302

@@ -296,6 +305,7 @@ private function outputTable( array $routes ): void
296305

297306
$this->output->newLine();
298307
$this->output->info( 'Total routes: ' . count( $routes ) );
308+
$this->output->info( 'Named routes: ' . $namedRoutes );
299309

300310
// Show method distribution
301311
$methods = array_count_values( array_column( $routes, 'method' ) );

0 commit comments

Comments
 (0)