Utopia HTTP Framework

repository·main·Indexed 43 days ago

https://github.com/utopia-php/http

A PHP MVC-based framework for secure and simple web development. It features a strict separation between routing/lifecycle concerns and resource management, supporting both PHP-FPM and Swoole server adapters. Key features include a dependency injection container, request lifecycle hooks (Init, Shutdown, Error), route parameter validation, and endpoint grouping.

Tokens
13.9K
Snippets
57
Records
67
Agent score
55%

What's inside Utopia HTTP

  1. Manage dependencies with Resources

    main

    Resources are used to prepare dependencies (like database connections or shared services) for requests. They are managed via a Utopia\DI\Container.

    • Registration: Use $resources->set('key', callback) to define a dependency.
    • Injection: Use ->inject('key') on a route or hook to make the resource available in the action closure.

    Note: Resources are for dependency preparation. For functional logic (like checking a session), use Hooks instead.

    // 1. Define a resource
    $resources->set('bootTime', function () {
        return \microtime(true);
    });
    
    // 2. Inject into an endpoint action
    Http::get('/')
        ->inject('bootTime')
        ->inject('response')
        ->action(function(float $bootTime, Response $response) {
            $response->send('Process started at: ' . \strval($bootTime));
        });
    
    // 3. Inject into a hook
    Http::shutdown()
        ->inject('bootTime')
        ->action(function(float $bootTime) {
            $uptime = \microtime(true) - $bootTime;
            \var_dump("Process uptime: " . $uptime . " seconds");
        });
  2. Use groups to apply common behavior to endpoints

    main

    Groups allow you to categorize endpoints and apply specific hooks only to those categories. This prevents code duplication for logic like API key validation or public access control.

    1. Assign groups to an endpoint using ->groups(['group_name', ...]).
    2. Assign a hook to the same group using ->groups(['group_name']).
    // Assigning groups to an endpoint
    Http::get('/v1/health')
        ->groups(['api', 'public'])
        ->inject('response')
        ->action(function(Response $response) {
            $response->send('OK');
        });
    
    // Applying a hook only to the 'api' group
    Http::init()
        ->groups(['api'])
        ->inject('request')
        ->inject('response')
        ->action(function(Request $request, Response $response) {
            $apiKey = $request->getHeader('x-api-key', '');
            if(empty($apiKey)) {
                $response
                    ->setStatusCode(Response::STATUS_CODE_UNAUTHORIZED)
                    ->send('API key missing.');
            }
        });
  3. How hooks work in the request lifecycle

    main

    Hooks are actions that run during the request lifecycle. They are designed for functional logic (e.g., logging, authentication, error handling) rather than dependency preparation.

    There are three types of hooks:

    1. Init hooks: Executed before the route action.
    2. Shutdown hooks: Executed after the route action finishes, but before the application shuts down.
    3. Error hooks: Executed whenever an error occurs in the lifecycle.

    By default, hooks run for every route. You can restrict a hook to specific routes by assigning it to a Group.

    // Init hook
    Http::init()
        ->inject('request')
        ->action(function(Request $request) {
            \var_dump("Received: " . $request->getMethod() . ' ' . $request->getURI());
        });
    
    // Shutdown hook
    Http::shutdown()
        ->inject('response')
        ->action(function(Response $response) {
            \var_dump('Responding with status code: ' . $response->getStatusCode());
        });
    
    // Error hook
    Http::error()
        ->inject('error')
        ->inject('response')
        ->action(function(\Throwable $error, Response $response) {
            $response
                ->setStatusCode(500)
                ->send('Error occurred ' . $error);
        });
  4. Use Init and Shutdown hooks for lifecycle management

    main

    Utopia provides lifecycle hooks to execute code at specific stages of a request. These methods take three arguments:

    1. A callback function.
    2. An array of resources required by the callback (e.g., ['response']).
    3. An endpoint group (string) or '*' for a global scope.

    Init

    Http::init() runs at the beginning of a request. It is useful for authentication, authorization, or rate limiting. You can define global init hooks or group-specific ones.

    Shutdown

    Http::shutdown() runs after a request is completed. It is useful for cleanup tasks like closing database connections, logging, or recording statistics.

    // Global init for all groups
    Http::init(function($response) {
       // Auth checks
    }, ['response'], '*');
    
    // Group-specific init
    Http::init(function($response) {
       // Group logic
    }, ['response'], 'group1');
    
    // Global shutdown
    Http::shutdown(function($request) {
       // Cleanup
    }, ['request'], '*');
  5. Configure route parameters

    main

    Utopia supports three types of parameters, all of which are defined using the param() method. Parameters defined via param() are passed to the action() callback in the order they are declared.

    1. Path Parameters: Specified using :<param_name> in the route path (e.g., /users/:id).
    2. Query Parameters: Specified via ->param().
    3. Body Parameters: Specified via ->param() (used in POST/PUT requests).

    The param() method accepts:

    • $key: The name of the parameter.
    • $default: The default value.
    • $validator: A validator object (e.g., new Wildcard()).
    • $description: A string describing the parameter.
    • $optional: Boolean indicating if the parameter is optional.
    • $injections: (Internal/Advanced) resources required.
    Http::put('/todos/:id')
       ->param('id', "", new Wildcard(), 'id of the todo')
       ->param('task', "", new Wildcard(), 'name of the todo')
       ->param('is_complete', true, new Wildcard(), 'task complete or not')
       ->inject('response')
       ->action(
           function($id, $task, $is_complete, $response) {
               // Logic using $id, $task, and $is_complete
           }
       );
  6. Quickstart: Create a basic Hello World application

    main

    Initialize your application by setting up a Container for resources, defining routes using the Http class, and starting the server with an appropriate adapter.

    Note: You must use inject() to make the Request and Response objects available to your action closure.

    require_once __DIR__.'/../vendor/autoload.php';
    
    use Utopia\�DI\\Container;
    use Utopia\Http\Http;
    use Utopia\Http\Request;
    use Utopia\Http\Response;
    use Utopia\Http\Adapter\FPM\\Server;
    
    $resources = new Container();
    
    Http::get('/hello-world') // Define Route
        ->inject('request')
        ->inject('response')
        ->action(
            function(Request $request, Response $response) {
                $response
                  ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
                  ->addHeader('Expires', '0')
                  ->addHeader('Pragma', 'no-cache')
                  ->json(['Hello' => 'World']);
            }
        );
    
    Http::setMode(Http::MODE_TYPE_PRODUCTION);
    
    $http = new Http(new Server($resources), 'America/New_York');
    $http->start();
  7. Configure Swoole server adapter

    main

    Use the Utopia\Http\Adapter\Swoole\Server adapter for high-performance Swoole environments. Unlike FPM, you pass the host and port directly to the Server constructor.

    use Utopia\DI\Container;
    use Utopia\Http\Http;
    use Utopia\Http\Request;
    use Utopia\Http\Response;
    use Utopia\Http\Adapter\Swoole\\Server;
    
    $resources = new Container();
    
    Http::get('/')
        ->inject('request')
        ->inject('response')
        ->action(
            function(Request $request, Response $response) {
                $response->send('Hello from Swoole');
            }
        );
    
    $http = new Http(new Server('0.0.0.0', '80', resources: $resources), 'America/New_York');
    $http->start();

    To run locally (requires Swoole extension):

    php src/server.php
  8. Configure PHP FPM server adapter

    main

    Use the Utopia\Http\Adapter\FPM\Server adapter for standard PHP-FPM environments. You can run the server locally using the built-in PHP server command.

    use Utopia\DI\Container;
    use Utopia\Http\Http;
    use Utopia\Http\Response;
    use Utopia\Http\Adapter\FPM\Server;
    
    $resources = new Container();
    
    Http::get('/')
        ->inject('response')
        ->action(
            function(Response $response) {
                $response->send('Hello from PHP FPM');
            }
        );
    
    $http = new Http(new Server($resources), 'America/New_York');
    $http->start();

    To run locally:

    php -S localhost:80 src/server.php
  9. Define basic routes and actions

    main

    Routes are defined using static methods on the Http class (e.g., get(), post(), put(), delete()). To make dependencies like $request or $response available to your controller, you must use the inject() method. The logic for the route is defined within the action() method via a callback function. The parameters passed to inject() must be included in the action() callback in the same order.

    use Utopia\\http\Http;
    use Utopia\Http\Request;
    use Utopia\Http\Response;
    use Utopia\Http\Adapter\Swoole\Server;
    
    Http::get('/')
       ->inject('request')
       ->inject('response')
       ->action(
           function(Request $request, Response $response) {
               $response->send("<div> Hello World! </div>");
           }
       );
    
    $http = new Http(new Server("0.0.0.0", "8080"), 'America/Toronto');
    $http->start();
  10. Router path tokens and wildcards

    main

    The Router uses specific tokens for path matching:

    • PLACEHOLDER_TOKEN: Used in paths to define parameters (e.g., /user/:::id). The default value is :::.
    • WILDCARD_TOKEN: Used for matching any segment or the entire path. The default value is *.