DALT.PHP

Routing System

Learn how URLs map to controllers

Lesson 2: Routing System

The router is the traffic controller of your application, directing requests to the right destination.

What is Routing?

Routing maps incoming URLs to controller files:

URL: /posts/123

Route: /posts/\{id\}

Controller: posts/show.php

Result: Display post #123

Defining Routes

Routes are defined in routes/routes.php:

$router = new Router();

// Basic routes
$router->get('/', 'welcome.php');
$router->get('/about', 'about.php');

// Routes with parameters
$router->get('/posts/\{id\}', 'posts/show.php');
$router->get('/users/\{id\}/posts', 'users/posts.php');

// Different HTTP methods
$router->post('/posts', 'posts/store.php');
$router->delete('/posts/\{id\}', 'posts/destroy.php');

Route Parameters

Extract dynamic values from URLs:

// Route definition
$router->get('/posts/\{id\}', 'posts/show.php');

// In controller (posts/show.php)
$id = $_GET['id']; // Automatically injected by router

$post = $db->query('SELECT * FROM posts WHERE id = :id', [
    'id' => $id
])->findOrFail();

Multiple Parameters

$router->get('/users/\{userId\}/posts/\{postId\}', 'users/posts/show.php');

// Accessing in controller
$userId = $_GET['userId'];
$postId = $_GET['postId'];

Route Order Matters! ⚠️

This is the most common routing bug:

// ❌ WRONG - Generic route first
$router->get('/posts/\{id\}', 'posts/show.php');
$router->get('/posts/create', 'posts/create.php'); // Never matches!

// ✅ CORRECT - Specific route first
$router->get('/posts/create', 'posts/create.php');
$router->get('/posts/\{id\}', 'posts/show.php');

Why? The router checks routes in order. /posts/create matches /posts/\{id\} with id = "create".

Common Bug: Always put specific routes before generic routes with parameters!

How Route Matching Works

The router uses regex to match patterns:

// Pattern: /posts/\{id\}
// Regex: #^/posts/([^/]+)$#

// Matches:
 /posts/123
 /posts/abc
 /posts/hello-world

// Doesn't match:
 /posts
 /posts/123/edit
 /post/123

HTTP Methods

Different methods for different actions:

// GET - Retrieve data
$router->get('/posts', 'posts/index.php');
$router->get('/posts/\{id\}', 'posts/show.php');

// POST - Create data
$router->post('/posts', 'posts/store.php');

// PATCH - Update data
$router->patch('/posts/\{id\}', 'posts/update.php');

// DELETE - Delete data
$router->delete('/posts/\{id\}', 'posts/destroy.php');

Method Spoofing

HTML forms only support GET and POST, so we use method spoofing:

<form method="POST" action="/posts/123">
    <input type="hidden" name="_method" value="DELETE">
    <button type="submit">Delete</button>
</form>
// Router checks for _method
$method = $_POST['_method'] ?? $_SERVER['REQUEST_METHOD'];

Middleware on Routes

Protect routes with middleware:

// Require authentication
$router->get('/dashboard', 'dashboard.php')->only('auth');

// Multiple middleware
$router->post('/posts', 'posts/store.php')->only(['auth', 'csrf']);

// Guest only (not authenticated)
$router->get('/login', 'auth/login.php')->only('guest');

RESTful Routing

Follow REST conventions for resource routes:

MethodURIControllerAction
GET/postsposts/index.phpList all posts
GET/posts/createposts/create.phpShow create form
POST/postsposts/store.phpStore new post
GET/posts/{id}posts/show.phpShow single post
GET/posts/{id}/editposts/edit.phpShow edit form
PATCH/posts/{id}posts/update.phpUpdate post
DELETE/posts/{id}posts/destroy.phpDelete post

Remember: /posts/create and /posts/\{id\}/edit must come before /posts/\{id\}!

Debugging Routes

View All Routes

// In routes/routes.php
dd($router->routes);

Output:

[
    'GET' => [
        '/' => 'welcome.php',
        '/posts/\{id\}' => 'posts/show.php',
    ],
    'POST' => [
        '/posts' => 'posts/store.php',
    ]
]

Test Route Matching

// Add to Router::route() method
dd([
    'uri' => $uri,
    'method' => $method,
    'matched' => $matchedRoute ?? 'none'
]);

Common Route Issues

404 on valid route:

  • Check route is registered
  • Verify HTTP method matches
  • Check route order

Wrong controller executed:

  • Check route order (specific before generic)
  • Verify route pattern is correct

Parameters not available:

  • Check $_GET in controller
  • Verify route has parameter placeholder

Practice Examples

Blog Routes

// List all posts
$router->get('/posts', 'posts/index.php');

// Create post form
$router->get('/posts/create', 'posts/create.php')->only('auth');

// Store new post
$router->post('/posts', 'posts/store.php')->only(['auth', 'csrf']);

// Show single post
$router->get('/posts/\{id\}', 'posts/show.php');

// Edit post form
$router->get('/posts/\{id\}/edit', 'posts/edit.php')->only('auth');

// Update post
$router->patch('/posts/\{id\}', 'posts/update.php')->only(['auth', 'csrf']);

// Delete post
$router->delete('/posts/\{id\}', 'posts/destroy.php')->only(['auth', 'csrf']);

User Profile Routes

$router->get('/users/\{id\}', 'users/show.php');
$router->get('/users/\{id\}/posts', 'users/posts.php');
$router->get('/users/\{id\}/edit', 'users/edit.php')->only('auth');

Ready for the Challenge?

Now that you understand routing, try fixing the broken routing challenge!

On this page