Skip to content

Boundaries

Boundaries are used to handle cross-cutting concerns on a protocol level. Similar to middleware, but is modelled to be immutable "taps" by default.

All examples are written using the ApiBoundary, for WebBoundary or other boundaries you would do the exact same thing, there's no difference other than which controller/handler they apply to.

TIP

A more generic HttpBoundary will likely be added in the future after support for more complicated priority orders and pattern matching has been implemented.

php
#[ApiBoundary]
class HttpConfig
{
}
Exception handlers

Exception handlers can be dynamically configured through the ExceptionHandler attribute. Any functions you register with this attribute will automatically be used to resolve http responses when that exception occurs.

The example provided demonstrates a simple way of handling failed authentication.

php
#[ApiBoundary]
class AuthorizationHandler
{
    #[OnException]
    public function handleAuthorizationException(AuthorizationException $ex): HttpResponse
    {
        return HttpResponseFactory::status(401);
    }
}
Enter Hooks (request interceptors)

Building on our exception handler example, let's define a request interceptor that handles basic token based authentication.

php
#[ApiBoundary]
class HttpConfig
{
    // Dependency injection is supported, but keep in mind that controller hints are
    // instantiated on startup and are not contextually exclusive to the current request.
    public function __construct(
        private readonly UserService $users,
    )
    {
    }

    /**
     * @throws AuthorizationException
     */
    #[OnEnter]
    public function checkAuthorization(SpanHttp $span): void
    {
        // Extract data from the original request.
        $token = Token::extract($span->getRequest()->header('Authorization'));

        // Check if we're authenticated.
        if ($token->isValid()) {
            // Set the auth context in the contextual Span to be used later.
            $span->setAuth(new AuthorizationProvider($this->users->get($token->sub)));
            return;
        }

        // Otherwise, check if the endpoints require authentication.
        if (!Str::startsWith($span->getRequest()->path(), ['/api/swagger-ui', '/api/health'])) {
            // If the endpoint is not public, throw an AuthorizationException for our
            // exception handler defined in the previous example.
            throw new AuthorizationException('Unauthenticated');
        }
    }
}
Exit Hooks (response interceptors)

Finally, response interceptors can be used to handle cross-cutting concerns that should happen after a request, such as logging, etc.

php
#[ApiBoundary]
class HttpConfig
{
    #[OnExit]
    public function logRequests(SpanHttp $span): void
    {
        // Example: log handled requests.
        // Tracing information included when logging through contextual spans.
        $span->debug($span->getRequest()->method() . " " . $span->getRequest()->path());
    }
}