Jun 5, 2026 · 2 min read · by Tim the Enchanter

Arc 0.9.1: Immutable-friendly HTTP and safer middleware

Image for Arc 0.9.1: Immutable-friendly HTTP and safer middleware

Arc 0.9.1 is a small but important release focused on safer HTTP primitives and middleware ergonomics.

What’s new

  • Request gains withAttribute(key, value), which returns a new instance with the attribute set. setAttribute() still exists and mutates in place; prefer withAttribute() in middleware pipelines.
  • Response adds immutable variants alongside the existing mutators: withStatusCode(int), withHeader(name, value), withContent(string), withAddedCookie(Cookie).
  • Response::redirect() reminder: external URLs are rejected by default to prevent open redirect attacks. Allow external targets only when you intend to (allowExternal: true).

Why this matters

  • Middleware often passes the same Request/Response through multiple layers. Mutating in place can create spooky-action-at-a-distance. The with* variants make behavior explicit and predictable.

PHP 8.5 clone-chain pitfall

  • PHP 8.5 has a gotcha where return clone(this)>method()canmutatethis)->method() can mutate this instead of the clone. Arc implements with* using a two-statement pattern to avoid this: new=clonenew = clone this; /* modify new\*/;returnnew \*/; return new.
  • If you add your own immutable helpers, avoid one-line clone chains on PHP 8.5.

Examples

Add security headers without mutating the original Response

use Arc\Http\MiddlewareInterface;
use Arc\Http\Request;
use Arc\Http\Response;

final class SecurityHeadersMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, callable $next): Response
    {
        $response = $next($request);

        return $response
            ->withHeader('X-Frame-Options', 'DENY')
            ->withHeader('X-Content-Type-Options', 'nosniff')
            ->withHeader('Referrer-Policy', 'no-referrer')
            ->withHeader('Permissions-Policy', 'camera=(), microphone=()');
    }
}

Attach request-scoped data immutably

final class AuthMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, callable $next): Response
    {
        $userId = $this->authenticate($request); // returns null or an ID
        $requestWithUser = $request->withAttribute('user_id', $userId);
        return $next($requestWithUser);
    }

    private function authenticate(Request $request): ?int { /* ... */ }
}

Release notes and code