PHP code example of bermudaphp / router

1. Go to this page and download the library: Download bermudaphp/router library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

bermudaphp / router example snippets


use Bermuda\Router\{Routes, Router, RouteRecord};

// Create routes collection
$routes = new Routes();
$router = Router::fromDnf($routes);

// Add route
$routes->addRoute(
    RouteRecord::get('hello', '/hello/[name]', function(string $name): string {
        return "Hello, $name!";
    })
);

// Match request
$route = $router->match('/hello/John', 'GET');
if ($route) {
    $name = $route->parameters->get('name');
    echo call_user_func($route->handler, $name);
}

// GET route for user listing
$routes->addRoute(RouteRecord::get('users.index', '/users', UsersController::class));

// POST route for creating new user
$routes->addRoute(RouteRecord::post('users.store', '/users', 'UsersController::store'));

// PUT route for full user update
$routes->addRoute(RouteRecord::put('users.update', '/users/[id]', 'UsersController::update'));

// PATCH route for partial user update
$routes->addRoute(RouteRecord::patch('users.patch', '/users/[id]', 'UsersController::patch'));

// DELETE route for user deletion
$routes->addRoute(RouteRecord::delete('users.destroy', '/users/[id]', 'UsersController::destroy'));

// Multiple methods for single route
$routes->addRoute(RouteRecord::any('users.resource', '/users/[id]', UsersController::class, 
    ['GET', 'PUT', 'PATCH', 'DELETE']
));

// All HTTP methods (catch-all route)
$routes->addRoute(RouteRecord::any('api.catchall', '/api/[path:.*]', ApiController::class));

// Closure as handler
$routes->addRoute(RouteRecord::get('hello', '/hello/[name]', function(string $name) {
    return "Hello, $name!";
}));

use Bermuda\Router\RouteBuilder;

$route = RouteBuilder::create('users.show', '/users/[id]')
    ->handler(UsersController::class)
    ->get()
    ->middleware([AuthMiddleware::class, ValidationMiddleware::class])
    ->tokens(['id' => '\d+'])
    ->defaults(['format' => 'json'])
    ->build();

$routes->addRoute($route);

// Required parameter
$routes->addRoute(RouteRecord::get('user.show', '/users/[id]', 'showUser'));

// Optional parameter
$routes->addRoute(RouteRecord::get('posts.index', '/posts/[?page]', 'listPosts'));

// Multiple parameters
$routes->addRoute(RouteRecord::get('post.show', '/blog/[year]/[month]/[slug]', 'showPost'));

// Inline pattern - numeric ID only
$routes->addRoute(RouteRecord::get('user.show', '/users/[id:\d+]', 'showUser'));

// Inline pattern - API version
$routes->addRoute(RouteRecord::get('api.version', '/api/[version:v\d+]/users', 'apiUsers'));

// Inline pattern - product SKU (3 letters, dash, 4 digits)
$routes->addRoute(RouteRecord::get('product.show', '/products/[sku:[A-Z]{3}-\d{4}]', 'showProduct'));

// Inline pattern - file format (only specific extensions)
$routes->addRoute(RouteRecord::get('download', '/files/[name]/[format:pdf|doc|txt]', 'downloadFile'));

// Optional inline pattern
$routes->addRoute(RouteRecord::get('posts.category', '/posts/[?category:tech|news|blog]', 'showCategory'));

// Complex inline pattern - date in YYYY-MM-DD format
$routes->addRoute(RouteRecord::get('archive.date', '/archive/[date:\d{4}-\d{2}-\d{2}]', 'showArchive'));

// Set pattern via method
$route = RouteRecord::get('product.show', '/products/[sku]', 'showProduct')
    ->withToken('sku', '[A-Z]{3}-\d{4}');

// Multiple patterns
$route = RouteRecord::get('complex.route', '/app/[locale]/[category]/[item]', 'handler')
    ->withTokens([
        'locale' => '[a-z]{2}(_[A-Z]{2})?',
        'category' => '[a-z0-9-]+', 
        'item' => '\d+'
    ]);

$route = RouteRecord::get('posts', '/posts/[?page]', 'listPosts')
    ->withDefaults([
        'page' => '1'
    ]);

// Single value
$route = $route->withDefaultValue('page', '1');

// Create group
$apiGroup = $routes->group('api', '/api/v1');

// Add routes to group
$apiGroup->get('users.index', '/users', UsersController::class);
$apiGroup->post('users.store', '/users', 'UsersController::store');
$apiGroup->get('users.show', '/users/[id]', 'UsersController::show');

// Resulting routes:
// api.users.index -> GET /api/v1/users
// api.users.store -> POST /api/v1/users  
// api.users.show -> GET /api/v1/users/[id]

// Middleware for entire group
$apiGroup->addMiddleware(AuthMiddleware::class)
         ->addMiddleware(RateLimitMiddleware::class);

// Patterns for entire group
$apiGroup->setTokens([
    'id' => '\d+',
    'slug' => '[a-z0-9-]+',
    'locale' => '[a-z]{2}'
]);

// Replace entire middleware stack
$apiGroup->setMiddleware([
    CorsMiddleware::class,
    AuthMiddleware::class,
    LoggingMiddleware::class
]);

// Simple generation
echo $router->generate('users.show', ['id' => 123]);
// Result: /users/123

// With optional parameters
echo $router->generate('posts.index', ['page' => 2]);
// Result: /posts/2

echo $router->generate('posts.index'); // optional parameter omitted
// Result: /posts

// Complex parameters
echo $router->generate('blog.post', [
    'year' => 2024,
    'month' => 3,
    'slug' => 'new-article'
]);
// Result: /blog/2024/3/new-article

use Bermuda\Router\Middleware\{MatchRouteMiddleware, DispatchRouteMiddleware, RouteNotFoundHandler};
use Bermuda\Pipeline\Pipeline;
use Bermuda\MiddlewareFactory\MiddlewareFactory;

$pipeline = new Pipeline();
$factory = new MiddlewareFactory($container, $responseFactory);

// Middleware for route matching
$pipeline->pipe($factory->makeMiddleware(MatchRouteMiddleware::class));

// Create 404 handler
$notFoundHandler = new RouteNotFoundHandler($responseFactory);

// Middleware for route execution with fallback handler
$pipeline->pipe(new DispatchRouteMiddleware($notFoundHandler));

$response = $pipeline->handle($request);

use Bermuda\Router\Middleware\RouteNotFoundHandler;

// JSON response mode (default)
$notFoundHandler = new RouteNotFoundHandler(
    $responseFactory,
    exceptionMode: false,
    customMessage: 'Requested resource not found'
);

// Example JSON response:
// {
//     "error": "Not Found",
//     "code": 404,
//     "message": "Requested resource not found",
//     "path": "/api/users/999",
//     "method": "GET",
//     "timestamp": "2024-12-25T10:30:00+00:00"
// }

// Exception mode
$notFoundHandler = new RouteNotFoundHandler(
    $responseFactory,
    exceptionMode: true // will throw RouteNotFoundException
);

// Dynamic mode switching via request attributes
$request = $notFoundHandler->withExceptionModeAttribute($request, true);

// Check current mode
$isExceptionMode = $notFoundHandler->getExceptionMode($request);

use Bermuda\Router\Middleware\{MatchRouteMiddleware, DispatchRouteMiddleware, RouteNotFoundHandler};

$pipeline = new Pipeline();

// 1. Try to find route
$pipeline->pipe(new MatchRouteMiddleware($middlewareFactory, $router));

// 2. Create 404 handler
$notFoundHandler = new RouteNotFoundHandler(
    $responseFactory, 
    exceptionMode: false,
    customMessage: 'API endpoint not found'
);

// 3. Execute found route or handle 404
$pipeline->pipe(new DispatchRouteMiddleware($notFoundHandler));

// Process request
$response = $pipeline->handle($request);

// With exceptionMode: true - exception handling
$notFoundHandler = new RouteNotFoundHandler($responseFactory, exceptionMode: true);
$pipeline->pipe(new DispatchRouteMiddleware($notFoundHandler));

try {
    $response = $pipeline->handle($request);
} catch (RouteNotFoundException $e) {
    // Custom exception handling (only works with exceptionMode: true)
    $response = new JsonResponse([
        'error' => 'Route not found',
        'path' => $e->path,
        'method' => $e->requestMethod
    ], 404);
}

// With exceptionMode: false (default) - automatic JSON response
$notFoundHandler = new RouteNotFoundHandler($responseFactory, exceptionMode: false);
$pipeline->pipe(new DispatchRouteMiddleware($notFoundHandler));

$response = $pipeline->handle($request);
// If route not found, RouteNotFoundHandler automatically returns JSON:
// HTTP 404 Not Found
// Content-Type: application/json; charset=utf-8
// {
//     "error": "Not Found",
//     "code": 404,
//     "message": "The requested endpoint was not found.",
//     "path": "/api/users/999",
//     "method": "GET", 
//     "timestamp": "2024-12-25T10:30:00+00:00"
// }

use Bermuda\Router\Middleware\RouteMiddleware;

class UserController
{
    public function show(ServerRequestInterface $request): ResponseInterface
    {
        // Get route data
        $routeMiddleware = RouteMiddleware::fromRequest($request);
        $route = $routeMiddleware->route;
        
        // Access parameters
        $userId = $request->getAttribute('id');
        // or
        $userId = $route->parameters->get('id');
        
        // Route information
        $routeName = $route->name;
        $routePath = $route->path;
        
        return new JsonResponse(['user_id' => $userId]);
    }
}

use Bermuda\Router\Locator\RouteLocator;

$locator = new RouteLocator(
    filename: '/app/config/routes.php',
    context: [
        'app' => $application,
        'container' => $container,
        'config' => $config
    ],
    useCache: $_ENV['APP_ENV'] === 'production'
);

$routes = $locator->getRoutes();

// /app/config/routes.php

/** @var Routes $routes */
/** @var Application $app */
/** @var ContainerInterface $container */

// Simple routes
$routes->addRoute(RouteRecord::get('home', '/', HomeController::class));

// Groups
$apiGroup = $routes->group('api', '/api/v1');
$apiGroup->addMiddleware(CorsMiddleware::class);

$apiGroup->get('users.index', '/users', function() use ($app) {
    return $app->getUsers();
});

$apiGroup->post('users.store', '/users', function($request) use ($container) {
    $service = $container->get(UserService::class);
    return $service->create($request->getParsedBody());
});

use Bermuda\Router\Attribute\Route;

class UserController
{
    #[Route('users.index', '/users', 'GET')]
    public function index(): ResponseInterface
    {
        // Get user list
        return new JsonResponse($this->userService->getAll());
    }

    #[Route('users.show', '/users/[id]', 'GET')]
    public function show(ServerRequestInterface $request): ResponseInterface
    {
        $id = $request->getAttribute('id');
        return new JsonResponse($this->userService->getById($id));
    }

    #[Route('users.store', '/users', 'POST', middleware: ['auth', 'validation'])]
    public function store(ServerRequestInterface $request): ResponseInterface
    {
        $data = $request->getParsedBody();
        $user = $this->userService->create($data);
        return new JsonResponse($user, 201);
    }

    #[Route('users.update', '/users/[id]', 'PUT|PATCH', group: 'api')]
    public function update(ServerRequestInterface $request): ResponseInterface
    {
        $id = $request->getAttribute('id');
        $data = $request->getParsedBody();
        $user = $this->userService->update($id, $data);
        return new JsonResponse($user);
    }

    #[Route('users.destroy', '/users/[id]', 'DELETE', priority: 10)]
    public function destroy(ServerRequestInterface $request): ResponseInterface
    {
        $id = $request->getAttribute('id');
        $this->userService->delete($id);
        return new JsonResponse(null, 204);
    }
}

use Bermuda\Router\Locator\{RouteLocator, AttributeRouteLocator};

// Base locator (file-based or any other)
$baseLocator = new RouteLocator('/app/config/routes.php');

// Decorate with attribute locator
$attributeLocator = new AttributeRouteLocator($baseLocator);

// Pass context (if needed)
$attributeLocator->setContext([
    'container' => $container,
    'config' => $config
]);

// Get all routes (file + attributes)
$routes = $attributeLocator->getRoutes();
$router = Router::fromDnf($routes);

use Bermuda\ClassFinder\{ClassFinder, ClassNotifier};
use Bermuda\Router\Locator\AttributeRouteLocator;
use Bermuda\Router\Attribute\Route;

// Create locator
$baseLocator = new RouteLocator('/app/config/routes.php');
$attributeLocator = new AttributeRouteLocator($baseLocator);

// Find all classes in controllers directory
$finder = new ClassFinder();
$classes = $finder->find('src/Controllers/');

// Notify locator about found classes (locator filters classes with Route attributes)
$notifier = new ClassNotifier([$attributeLocator]);
$notifier->notify($classes);

// Get all routes
$routes = $attributeLocator->getRoutes();
$router = Router::fromDnf($routes);

use Bermuda\ClassFinder\{ClassFinder, ClassNotifier};
use Bermuda\Router\Locator\{RouteLocator, AttributeRouteLocator};
use Bermuda\Router\Attribute\Route;

// 1. Create base locator
$baseLocator = new RouteLocator(
    filename: '/app/config/routes.php',
    useCache: $_ENV['APP_ENV'] === 'production'
);

// 2. Create attribute locator as decorator
$attributeLocator = new AttributeRouteLocator($baseLocator);
$attributeLocator->setContext(['app' => $app, 'container' => $container]);

// 3. Find classes in various directories with exclusions
$finder = new ClassFinder();

$controllerClasses = $finder->find(
    paths: [
        'src/Controllers/',     // Main controllers
        'src/Api/',             // API controllers
        'app/Http/Controllers/' // Legacy controllers
    ],
    exclude: ['src/Api/products'] // Exclude specific directory
);

// 4. Notify locator about found classes
// ClassFinder finds all classes in specified directories,
// then AttributeRouteLocator scans class methods for Route attributes
// and registers found methods as route handlers
$notifier = new ClassNotifier([$attributeLocator]);
$notifier->notify($controllerClasses);

// 5. Get all routes and create router
$routes = $attributeLocator->getRoutes();
$router = Router::fromDnf($routes);

// 6. Use in middleware pipeline
$pipeline = new Pipeline();
$pipeline->pipe(new MatchRouteMiddleware($middlewareFactory, $router));
$pipeline->pipe(new DispatchRouteMiddleware($notFoundHandler));

$response = $pipeline->handle($request);

// First define groups in base locator's routes.php file
/** @var Routes $routes */
$apiGroup = $routes->group('api', '/api');
$adminGroup = $routes->group('admin', '/admin');

// Now you can use these groups in attributes
class ApiController
{
    #[Route('api.users.index', '/users', 'GET', group: 'api')]
    public function getUsers(): ResponseInterface 
    {
        return new JsonResponse($this->userService->getAll());
    }

    #[Route('api.users.store', '/users', 'POST', group: 'api', middleware: ['auth'])]
    public function createUser(ServerRequestInterface $request): ResponseInterface 
    {
        $data = $request->getParsedBody();
        $user = $this->userService->create($data);
        return new JsonResponse($user, 201);
    }
}

// Group configuration can be added after route loading
$routes = $attributeLocator->getRoutes();

// Configure 'api' group after loading routes
$apiGroup = $routes->group('api');
$apiGroup->addMiddleware(CorsMiddleware::class);
$apiGroup->setTokens(['id' => '\d+']);

class RouteController  
{
    // Highest priority - special handling
    #[Route('admin.special', '/admin/special/action', 'POST', priority: 100)]
    public function specialAdminAction(): ResponseInterface 
    {
        return new JsonResponse(['action' => 'special']);
    }

    // High priority - specific route
    #[Route('user.profile', '/users/profile', 'GET', priority: 50)]
    public function userProfile(): ResponseInterface 
    {
        return new JsonResponse(['page' => 'profile']);
    }

    // Medium priority - route with parameter
    #[Route('user.show', '/users/[id]', 'GET', priority: 10)]
    public function showUser(): ResponseInterface 
    {
        return new JsonResponse(['type' => 'user']);
    }

    // Normal priority - general route
    #[Route('users.list', '/users', 'GET', priority: 0)]
    public function listUsers(): ResponseInterface 
    {
        return new JsonResponse(['type' => 'list']);
    }

    // Low priority - catch-all route (checked last)
    #[Route('catch.all', '/[path:.*]', 'GET', priority: -10)]
    public function catchAll(): ResponseInterface 
    {
        return new JsonResponse(['type' => 'fallback']);
    }
}

class ApiController
{
    // v2 API - high priority
    #[Route('api.v2.users', '/api/v2/users/[id]', 'GET', priority: 20)]
    public function getUserV2(): ResponseInterface 
    {
        return new JsonResponse(['version' => 'v2', 'features' => ['new_field']]);
    }

    // v1 API - medium priority  
    #[Route('api.v1.users', '/api/v1/users/[id]', 'GET', priority: 10)]
    public function getUserV1(): ResponseInterface 
    {
        return new JsonResponse(['version' => 'v1']);
    }

    // Fallback to v1 for requests without version - low priority
    #[Route('api.users.fallback', '/api/users/[id]', 'GET', priority: 0)]
    public function getUserFallback(): ResponseInterface 
    {
        // Redirect to v1
        return new JsonResponse(['version' => 'v1', 'deprecated' => true]);
    }
}

// Regular routes - addition order determines priority
$routes->addRoute(RouteRecord::get('special', '/api/special', 'handler1')); // Checked first
$routes->addRoute(RouteRecord::get('generic', '/api/[path:.*]', 'handler2')); // Checked second

// Attribute routes - use priority parameter
#[Route('high', '/api/high', 'GET', priority: 100)]    // Checked first
#[Route('low', '/api/low', 'GET', priority: 0)]        // Checked second

// Create route with middleware
$route = RouteRecord::get('users.show', '/users/[id]', UserController::class)
    ->withMiddlewares([AuthMiddleware::class, ValidationMiddleware::class]);

// Access full pipeline (middleware + handler)
$fullPipeline = $route->pipeline; // [AuthMiddleware::class, ValidationMiddleware::class, UserController::class]

// Access middleware only
$middleware = $route->middleware; // [AuthMiddleware::class, ValidationMiddleware::class]

// Access main handler
$handler = $route->handler; // UserController::class

use Bermuda\Router\CacheFileProvider;

// Setup routes
$routes = new Routes();
$routes->addRoute(RouteRecord::get('home', '/', 'HomeController'));
$routes->addRoute(RouteRecord::get('users.show', '/users/[id]', 'UsersController::show'));

// Create cache
$cacheProvider = new CacheFileProvider('/path/to/cache');
$routeData = $routes->toArray();

$cacheProvider->writeFile('routes', $routeData);

use Bermuda\Router\RoutesCache;

// Load cached routes
$cacheProvider = new CacheFileProvider('/path/to/cache');
$cachedData = $cacheProvider->readFile('routes');

$routes = new RoutesCache($cachedData);
$router = Router::fromDnf($routes);

// When creating routes with closures
$app = new Application();
$db = new Database();

$routes->addRoute(RouteRecord::get('users.index', '/users', 
    function() use ($app, $db) {
        return $app->respond($db->users()->all());
    }
));

// Save cache with context
$cacheProvider->writeFile('routes', $routes->toArray(), [
    'app' => $app,
    'db' => $db
]);

// Load with context - variables $app and $db will be available in cached file
$cachedData = $cacheProvider->readFile('routes');
$routes = new RoutesCache($cachedData);

// 1. Objects as handlers
$controller = new UserController();
$routes->addRoute(RouteRecord::get('users', '/users', $controller)); // Not cached

// 2. Closures with objects in use (without context)
$service = new UserService();
$routes->addRoute(RouteRecord::get('users', '/users', 
    function() use ($service) { 
        return $service->getUsers();
    }
)); // Handler will be cached, but error will occur due to missing context // Undefined variable $service

// 3. Anonymous classes
$routes->addRoute(RouteRecord::get('test', '/test', new class {
    public function handle() { return 'test'; }
})); // Anonymous class not cached

// 1. Strings (class and method names)
$routes->addRoute(RouteRecord::get('users', '/users', 'UserController'));
$routes->addRoute(RouteRecord::get('posts', '/posts', 'PostController::index'));

// 2. Arrays with class/method names
$routes->addRoute(RouteRecord::get('api', '/api', ['ApiController', 'handle']));

// 3. Scalar values in context
$routes->addRoute(RouteRecord::get('config', '/config',
    function() use ($appName, $version) { // $appName and $version - strings/numbers
        return ['app' => $appName, 'version' => $version];
    }
));

use Bermuda\Router\Exception\{
    RouterException,
    RouteNotFoundException, 
    RouteNotRegisteredException,
    GeneratorException,
    MatchException
};

try {
    $route = $router->match($uri, $method);
    $url = $router->generate('nonexistent.route', ['id' => 123]);
} catch (RouteNotFoundException $e) {
    // 404 - route not found
    echo "Path not found: $e->path [$e->requestMethod]";
} catch (RouteNotRegisteredException $e) {
    // 500 - route not registered 
    echo "Route '$e->routeName' not registered";
} catch (GeneratorException $e) {
    // 400 - URL generation error
    echo "URL generation error: " . $e->getMessage();
} catch (MatchException $e) {
    // Pattern matching error
    echo "Matching error: $e->pattern for $e->path";
} catch (RouterException $e) {
    // General router errors
    echo "Router error: " . $e->getMessage();
}