ApnaPHP

Documentation

Middleware

Middleware in ApnaPHP provides a convenient mechanism for filtering HTTP requests entering your application. Middleware can perform tasks such as authentication, logging, CSRF protection, and more.

Creating Middleware

Using CLI Command

Create middleware using the CLI command:

php apna make:middleware AuthMiddleware

This creates a new middleware file at app/middleware.apna.php or in a specific directory.

Manual Creation

Create middleware files manually by adding middleware.apna.php files in your application directories.

Middleware Structure

Basic Middleware File

<?php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

// Check if user is authenticated
if (!auth()->check()) {
    return redirect('/login')->withError('Please login to continue.');
}

// If no response is returned, continue to the next middleware or route

Advanced Middleware with Logic

<?php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class AuthMiddleware
{
    public function handle(Request $request)
    {
        // Get the authorization header
        $token = $request->header('Authorization');
        
        if (!$token) {
            return Response::json(['error' => 'Unauthorized'], 401);
        }

        // Validate the token
        if (!$this->validateToken($token)) {
            return Response::json(['error' => 'Invalid token'], 401);
        }

        // Set user context
        $this->setUserContext($request);

        // Continue to next middleware
        return null;
    }

    private function validateToken($token)
    {
        // Your token validation logic here
        return true; // Simplified for example
    }

    private function setUserContext($request)
    {
        // Set user information in request context
        $request->setAttribute('user_id', 123);
    }
}

Middleware Hierarchy

ApnaPHP uses a hierarchical middleware system where middleware files are applied based on their location in the file structure.

Directory Structure

app/
├── middleware.apna.php              # Global middleware (all routes)
├── api/
│   └── middleware.apna.php         # API routes middleware
├── admin/
│   └── middleware.apna.php         # Admin routes middleware
└── dashboard/
    └── middleware.apna.php         # Dashboard routes middleware

Execution Order

  1. Global Middleware (app/middleware.apna.php)
  2. Directory Middleware (nested directories)
  3. Route-Specific Middleware (if any)

Built-in Middleware

Authentication Middleware

<?php
// app/admin/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

// Check if user is authenticated
if (!auth()->check()) {
    return redirect('/login')->withError('Authentication required.');
}

// Check if user has admin role
if (!auth()->user()->isAdmin()) {
    return Response::json(['error' => 'Admin access required'], 403);
}

CSRF Protection Middleware

<?php
// app/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

// Skip CSRF for GET requests
if ($request->method() === 'GET') {
    return null;
}

// Skip CSRF for API routes
if ($request->path()->startsWith('/api/')) {
    return null;
}

// Verify CSRF token
$token = $request->input('_token') ?? $request->header('X-CSRF-TOKEN');
$sessionToken = session('_token');

if (!$token || !hash_equals($sessionToken, $token)) {
    return Response::json(['error' => 'CSRF token mismatch'], 419);
}

Rate Limiting Middleware

<?php
// app/api/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class RateLimitMiddleware
{
    private $maxRequests = 100;
    private $timeWindow = 3600; // 1 hour

    public function handle(Request $request)
    {
        $clientIp = $request->ip();
        $key = "rate_limit:{$clientIp}";
        
        // Get current request count
        $currentRequests = cache()->get($key, 0);
        
        if ($currentRequests >= $this->maxRequests) {
            return Response::json([
                'error' => 'Rate limit exceeded',
                'retry_after' => $this->timeWindow
            ], 429);
        }

        // Increment request count
        cache()->put($key, $currentRequests + 1, $this->timeWindow);
        
        return null;
    }
}

Logging Middleware

<?php
// app/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class LoggingMiddleware
{
    public function handle(Request $request)
    {
        $startTime = microtime(true);
        
        // Log request
        logger()->info('Request started', [
            'method' => $request->method(),
            'url' => $request->fullUrl(),
            'ip' => $request->ip(),
            'user_agent' => $request->userAgent()
        ]);

        // Store start time for response logging
        $request->setAttribute('start_time', $startTime);
        
        return null;
    }

    public function terminate(Request $request, Response $response)
    {
        $startTime = $request->getAttribute('start_time');
        $duration = microtime(true) - $startTime;

        // Log response
        logger()->info('Request completed', [
            'method' => $request->method(),
            'url' => $request->fullUrl(),
            'status' => $response->getStatusCode(),
            'duration' => round($duration * 1000, 2) . 'ms'
        ]);
    }
}

Common Middleware Patterns

API Authentication

<?php
// app/api/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class ApiAuthMiddleware
{
    public function handle(Request $request)
    {
        $apiKey = $request->header('X-API-Key');
        
        if (!$apiKey) {
            return Response::json(['error' => 'API key required'], 401);
        }

        // Validate API key
        $user = db()->table('api_keys')
                   ->where('key', $apiKey)
                   ->where('is_active', true)
                   ->first();

        if (!$user) {
            return Response::json(['error' => 'Invalid API key'], 401);
        }

        // Set authenticated user
        $request->setAttribute('api_user', $user);
        
        return null;
    }
}

Admin Access Control

<?php
// app/admin/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

// Check authentication
if (!auth()->check()) {
    return redirect('/admin/login')->withError('Please login.');
}

$user = auth()->user();

// Check admin role
if (!$user->isAdmin()) {
    return Response::json(['error' => 'Admin access required'], 403);
}

// Check specific permissions
$requiredPermission = $this->getRequiredPermission($request);
if (!$user->hasPermission($requiredPermission)) {
    return Response::json(['error' => 'Insufficient permissions'], 403);
}

private function getRequiredPermission($request)
{
    $method = strtolower($request->method());
    $path = $request->path();
    
    // Map routes to permissions
    $permissions = [
        'get' => 'read',
        'post' => 'create',
        'put' => 'update',
        'delete' => 'delete'
    ];
    
    $action = $permissions[$method] ?? 'read';
    return "admin.{$action}";
}

Maintenance Mode

<?php
// app/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

// Check if maintenance mode is enabled
if (env('MAINTENANCE_MODE', false)) {
    // Allow access for specific IPs
    $allowedIPs = explode(',', env('MAINTENANCE_ALLOWED_IPS', ''));
    
    if (!in_array($request->ip(), $allowedIPs)) {
        return Response::html('<h1>Site Under Maintenance</h1>', 503);
    }
}

Request Validation

<?php
// app/api/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class ValidationMiddleware
{
    public function handle(Request $request)
    {
        // Only validate POST/PUT/PATCH requests
        if (!in_array($request->method(), ['POST', 'PUT', 'PATCH'])) {
            return null;
        }

        $rules = $this->getValidationRules($request);
        
        if ($rules) {
            $validator = validate($request->all(), $rules);
            
            if ($validator->fails()) {
                return Response::json([
                    'error' => 'Validation failed',
                    'errors' => $validator->errors()
                ], 422);
            }
        }
        
        return null;
    }

    private function getValidationRules($request)
    {
        // Define validation rules based on route
        $route = $request->path();
        
        $rules = [
            '/api/users' => [
                'name' => 'required|string|max:255',
                'email' => 'required|email|unique:users,email'
            ],
            '/api/posts' => [
                'title' => 'required|string|max:255',
                'content' => 'required|string'
            ]
        ];
        
        return $rules[$route] ?? null;
    }
}

Middleware Responses

Redirect Response

// Redirect to login page
return redirect('/login')->withError('Please login first.');

// Redirect with success message
return redirect('/dashboard')->withSuccess('Welcome back!');

// Redirect with input data
return redirect()->back()->withInput();

JSON Response

// Success response
return Response::json(['message' => 'Success'], 200);

// Error response
return Response::json(['error' => 'Unauthorized'], 401);

// Validation error
return Response::json([
    'error' => 'Validation failed',
    'errors' => $validator->errors()
], 422);

HTML Response

// Custom error page
return Response::html('<h1>Access Denied</h1>', 403);

// Maintenance page
return Response::html(
    '<h1>Site Under Maintenance</h1><p>We will be back soon!</p>', 
    503
);

Middleware Testing

Unit Testing Middleware

<?php
// tests/Middleware/AuthMiddlewareTest.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

class AuthMiddlewareTest extends TestCase
{
    public function test_authenticated_user_passes()
    {
        // Mock authenticated user
        auth()->login(User::find(1));
        
        $request = new Request();
        $middleware = new AuthMiddleware();
        
        $response = $middleware->handle($request);
        
        $this->assertNull($response); // Should continue
    }

    public function test_unauthenticated_user_redirects()
    {
        $request = new Request();
        $middleware = new AuthMiddleware();
        
        $response = $middleware->handle($request);
        
        $this->assertInstanceOf(Response::class, $response);
        $this->assertEquals(302, $response->getStatusCode());
    }
}

Best Practices

1. Keep Middleware Focused

Each middleware should have a single responsibility:

// Good: Single responsibility
class AuthMiddleware
{
    public function handle($request)
    {
        if (!auth()->check()) {
            return redirect('/login');
        }
    }
}

// Bad: Multiple responsibilities
class AuthAndLoggingMiddleware
{
    public function handle($request)
    {
        // Authentication logic
        // Logging logic
        // Rate limiting logic
    }
}

2. Use Early Returns

// Good: Early returns for clarity
if (!$token) {
    return Response::json(['error' => 'Token required'], 401);
}

if (!$this->validateToken($token)) {
    return Response::json(['error' => 'Invalid token'], 401);
}

// Continue processing...

// Bad: Nested conditions
if ($token) {
    if ($this->validateToken($token)) {
        // Continue processing...
    } else {
        return Response::json(['error' => 'Invalid token'], 401);
    }
} else {
    return Response::json(['error' => 'Token required'], 401);
}

3. Cache Expensive Operations

class PermissionMiddleware
{
    public function handle($request)
    {
        $userId = auth()->id();
        $permission = $this->getRequiredPermission($request);
        
        // Cache permission check
        $cacheKey = "user_permissions:{$userId}";
        $hasPermission = cache()->remember($cacheKey, 300, function() use ($userId, $permission) {
            return User::find($userId)->hasPermission($permission);
        });
        
        if (!$hasPermission) {
            return Response::json(['error' => 'Forbidden'], 403);
        }
    }
}

4. Use Configuration for Flexibility

class RateLimitMiddleware
{
    private $config;

    public function __construct()
    {
        $this->config = [
            'max_requests' => env('RATE_LIMIT_MAX', 100),
            'time_window' => env('RATE_LIMIT_WINDOW', 3600),
            'skip_ips' => explode(',', env('RATE_LIMIT_SKIP_IPS', ''))
        ];
    }

    public function handle($request)
    {
        // Use configuration values
        $maxRequests = $this->config['max_requests'];
        $timeWindow = $this->config['time_window'];
        
        // Skip certain IPs
        if (in_array($request->ip(), $this->config['skip_ips'])) {
            return null;
        }
        
        // Rate limiting logic...
    }
}

Common Use Cases

1. API Authentication & Authorization

<?php
// app/api/middleware.apna.php

// API Key authentication
$apiKey = $request->header('X-API-Key');
if (!$this->validateApiKey($apiKey)) {
    return Response::json(['error' => 'Invalid API key'], 401);
}

// Rate limiting
if (!$this->checkRateLimit($request)) {
    return Response::json(['error' => 'Rate limit exceeded'], 429);
}

2. Web Application Security

<?php
// app/middleware.apna.php

// CSRF protection
if (!$this->verifyCsrfToken($request)) {
    return Response::json(['error' => 'CSRF token mismatch'], 419);
}

// XSS protection headers
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');

3. Admin Panel Access

<?php
// app/admin/middleware.apna.php

// Require admin authentication
if (!auth()->check() || !auth()->user()->isAdmin()) {
    return redirect('/admin/login');
}

// Check specific admin permissions
$requiredPermission = $this->getRequiredPermission($request);
if (!auth()->user()->hasPermission($requiredPermission)) {
    return Response::json(['error' => 'Insufficient permissions'], 403);
}

This comprehensive guide covers all aspects of middleware in ApnaPHP, from basic usage to advanced patterns and best practices.