Routing

Routes define how your application responds to HTTP requests. ZephyrPHP provides a powerful, expressive routing system with all the features you need.

Basic Routing

Routes are defined in routes/web.php. The simplest route accepts a URI and a callback:

<?php

use ZephyrPHP\Router\Route;

// Closure-based route
Route::get('/hello', function() {
    return 'Hello, World!';
});

// Controller string syntax (recommended)
Route::get('/users', 'UserController@index');

// Controller array syntax
Route::get('/users', [UserController::class, 'index']);

Available HTTP Methods

ZephyrPHP supports all common HTTP methods:

Route::get('/users', 'UserController@index');
Route::post('/users', 'UserController@store');
Route::put('/users/{id}', 'UserController@update');
Route::patch('/users/{id}', 'UserController@update');
Route::delete('/users/{id}', 'UserController@destroy');
Route::options('/users', 'UserController@options');

// Match multiple methods
Route::match(['get', 'post'], '/form', 'FormController@handle');

// Match all methods
Route::any('/webhook', 'WebhookController@handle');
Method Description
GET Retrieve a resource
POST Create a new resource
PUT Update an entire resource
PATCH Partially update a resource
DELETE Delete a resource

Route Parameters

Capture segments of the URI using route parameters:

Required Parameters

Route::get('/users/{id}', function($id) {
    return "User ID: $id";
});

Route::get('/posts/{post}/comments/{comment}', function($postId, $commentId) {
    return "Post: $postId, Comment: $commentId";
});

Optional Parameters

Make parameters optional by adding a ? suffix:

Route::get('/users/{name?}', function($name = 'Guest') {
    return "Hello, $name!";
});

Parameter Constraints

Restrict parameters using inline regex or constraint helpers:

// Inline regex constraint
Route::get('/users/{id:\d+}', 'UserController@show');
Route::get('/posts/{slug:[a-z-]+}', 'PostController@show');

// Using constraint helpers
Route::get('/users/{id}', 'UserController@show')
    ->whereNumber('id');

Route::get('/posts/{slug}', 'PostController@show')
    ->whereSlug('slug');

Route::get('/category/{type}', 'CategoryController@show')
    ->whereIn('type', ['tech', 'news', 'sports']);

// Multiple constraints
Route::get('/articles/{id}/{slug}', 'ArticleController@show')
    ->whereNumber('id')
    ->whereSlug('slug');

Available Constraint Helpers

Method Pattern Example
whereNumber() \d+ 123, 456
whereAlpha() [a-zA-Z]+ john, Admin
whereAlphaNumeric() [a-zA-Z0-9]+ user123
whereSlug() [a-z0-9-]+ my-post-title
whereUuid() UUID format 550e8400-e29b-41d4-a716-446655440000
whereIn() Specific values draft, published
where() Custom regex Any pattern

Global Patterns

Define patterns that apply to all routes:

// In your routes file or service provider
Route::pattern('id', '\d+');
Route::pattern('slug', '[a-z0-9-]+');

// Now all {id} parameters will be numeric
Route::get('/users/{id}', 'UserController@show');
Route::get('/posts/{id}', 'PostController@show');

Named Routes

Named routes allow you to generate URLs or redirects without hardcoding URIs:

Route::get('/users/{id}', 'UserController@show')
    ->name('users.show');

Route::get('/dashboard', 'DashboardController@index')
    ->name('dashboard');

Generating URLs

Use the route() helper to generate URLs for named routes:

// Simple route
$url = route('dashboard');
// Result: /dashboard

// Route with parameters
$url = route('users.show', ['id' => 1]);
// Result: /users/1

// Absolute URL
$url = Route::url('users.show', ['id' => 1], absolute: true);
// Result: https://example.com/users/1

Checking Current Route

// Get current route name
$name = Route::currentRouteName();

// Check if current route matches
if (Route::is('users.*')) {
    // On a users route
}

if (Route::is('admin.dashboard', 'admin.settings')) {
    // On admin dashboard or settings
}

Redirecting to Named Routes

// In a controller
return $this->redirectToRoute('users.show', ['id' => $user->id]);

Route Groups

Group routes that share common attributes like prefixes or middleware:

Simple Prefix Groups

Route::group('/admin', function() {
    Route::get('/dashboard', 'AdminController@dashboard');
    Route::get('/users', 'AdminController@users');
    Route::get('/settings', 'AdminController@settings');
});

// Results in:
// /admin/dashboard
// /admin/users
// /admin/settings

Fluent Group Builder

Route::prefix('/api/v1')
    ->middleware(['api', 'throttle'])
    ->group(function() {
        Route::get('/users', 'Api\UserController@index');
        Route::get('/posts', 'Api\PostController@index');
    });

// Combine with domain
Route::domain('admin.example.com')
    ->prefix('/dashboard')
    ->middleware('admin')
    ->group(function() {
        Route::get('/', 'AdminController@index');
    });

Array Syntax

Route::group([
    'prefix' => '/admin',
    'middleware' => ['auth', 'admin'],
    'domain' => 'admin.example.com',
], function() {
    Route::get('/dashboard', 'AdminController@dashboard');
});

Resource Routes

Quickly register all CRUD routes for a resource:

// Full resource (7 routes)
Route::resource('users', 'UserController');

// API resource (5 routes - no create/edit)
Route::apiResource('posts', 'PostController');

// Multiple resources
Route::resources([
    'users' => 'UserController',
    'posts' => 'PostController',
]);

Resource Route Options

// Only specific actions
Route::resource('photos', 'PhotoController', [
    'only' => ['index', 'show']
]);

// Exclude specific actions
Route::resource('videos', 'VideoController', [
    'except' => ['destroy']
]);

// With middleware
Route::resource('articles', 'ArticleController', [
    'middleware' => ['auth']
]);

Resource Routes Table

Method URI Action Route Name
GET/usersindexusers.index
GET/users/createcreateusers.create
POST/usersstoreusers.store
GET/users/{user}showusers.show
GET/users/{user}/editeditusers.edit
PUT/PATCH/users/{user}updateusers.update
DELETE/users/{user}destroyusers.destroy

Route Model Binding

Automatically resolve route parameters to model instances:

// Register model binding
Route::model('user', App\Models\User::class);
Route::model('post', App\Models\Post::class);

// Now {user} will be resolved to a User model
Route::get('/users/{user}', function(User $user) {
    return $user->name;
});

// Bind by different column
Route::model('post', App\Models\Post::class, 'slug');

// Custom binding logic
Route::bind('user', function($value) {
    return User::where('username', $value)->firstOrFail();
});

Fallback Routes

Define a fallback route for unmatched requests:

Route::fallback('FallbackController@handle');

// Or with closure
Route::fallback(function() {
    return view('errors/404');
});

Redirect Routes

Define redirect routes without a controller:

// Temporary redirect (302)
Route::redirect('/old-page', '/new-page');

// Permanent redirect (301)
Route::permanentRedirect('/legacy', '/modern');

// View route (renders template directly)
Route::view('/about', 'pages/about', ['title' => 'About Us']);

Subdomain Routing

Define routes for specific subdomains:

Route::domain('api.example.com')->group(function() {
    Route::get('/users', 'Api\UserController@index');
});

// Wildcard subdomain
Route::domain('{account}.example.com')->group(function() {
    Route::get('/dashboard', function($account) {
        return "Dashboard for: $account";
    });
});

Route Middleware

Attach middleware to individual routes:

Route::get('/admin', 'AdminController@index', ['auth', 'admin']);

// Or using method
Route::get('/profile', 'ProfileController@index')
    ->middleware('auth');

HTML Forms & Method Spoofing

HTML forms only support GET and POST. For PUT, PATCH, or DELETE, use method spoofing:

<form action="/users/1" method="POST">
    <input type="hidden" name="_method" value="DELETE">
    {{ csrf_field()|raw }}
    <button type="submit">Delete User</button>
</form>
CSRF Protection

All POST, PUT, PATCH, and DELETE requests require a CSRF token. Use {{ csrf_field()|raw }} in your forms.

Route Listing

Get a list of all registered routes:

// In code
$routes = Route::list();

// Each route contains:
// - method
// - path
// - name
// - middleware
// - domain

Route Caching

In production, cache your routes for better performance:

php craftsman route:cache

Clear the route cache with:

php craftsman route:clear
Note

Route caching only works with controller-based routes, not closures. If you use closure routes, they cannot be cached.

Complete Example

A typical routes file:

routes/web.php
<?php

use ZephyrPHP\Router\Route;

// Public routes
Route::get('/', 'HomeController@index')->name('home');
Route::get('/about', 'PageController@about')->name('about');
Route::view('/contact', 'pages/contact')->name('contact');

// Authentication
Route::get('/login', 'AuthController@showLogin')->name('login');
Route::post('/login', 'AuthController@login');
Route::post('/logout', 'AuthController@logout')->name('logout');

// Protected routes
Route::prefix('/dashboard')
    ->middleware('auth')
    ->group(function() {
        Route::get('/', 'DashboardController@index')->name('dashboard');
        Route::resource('posts', 'PostController');
        Route::resource('comments', 'CommentController');
    });

// Admin routes
Route::prefix('/admin')
    ->middleware(['auth', 'admin'])
    ->group(function() {
        Route::get('/', 'Admin\DashboardController@index')->name('admin.dashboard');
        Route::resource('users', 'Admin\UserController');
    });

// API routes
Route::prefix('/api/v1')->group(function() {
    Route::apiResource('posts', 'Api\PostController');
    Route::apiResource('users', 'Api\UserController');
});

// Fallback
Route::fallback('ErrorController@notFound');
Related

Learn how to handle requests in Controllers, protect routes with Middleware, or work with Request and Response objects.