Skip to content

Repository files navigation

Rivet PHP

Rivet PHP provides contract-first PHP authoring for Rivet. Define an HTTP API with PHP interfaces, native types, and attributes; Rivet PHP turns it into the canonical Rivet contract, then Rivet emits OpenAPI.

Requirements

  • PHP 8.2 or later; CI currently tests PHP 8.2, 8.3, and 8.4
  • Composer 2
  • Rivet 0.41.0, available as rivet on PATH or configured with --binary or RIVET_BINARY

Install

composer require rivet/php

Define A Contract

<?php

namespace App\Contract;

use Rivet\Attribute\Contract;
use Rivet\Attribute\Get;
use Rivet\Attribute\Post;
use Rivet\Attribute\Returns;
use Rivet\Parameter\Body;
use Rivet\Parameter\Path;

// The JSON request body accepted by POST /members.
final readonly class CreateMember
{
    public function __construct(public string $email) {}
}

// The successful response body returned by both endpoints.
final readonly class Member
{
    public function __construct(public string $id, public string $email) {}
}

// The error response body returned when a member does not exist.
final readonly class ProblemResponse
{
    public function __construct(public string $error) {}
}

// Group the endpoints under /members and name generated operations Members_*.
#[Contract('/members', name: 'Members')]
interface MembersContract
{
    // Name each complete endpoint path after its operation.
    public const FIND = '/members/{id}';
    public const CREATE = '/members';

    // Define GET /members/{id}.
    #[Get(self::FIND)]
    // In addition to the normal 200 Member response, document a typed 404.
    #[Returns(404, ProblemResponse::class)]
    // Bind {id} from the path and return a Member response body.
    public function get(#[Path] string $id): Member;

    // Define POST /members.
    #[Post(self::CREATE)]
    // Bind the JSON body to CreateMember; POST success defaults to 201.
    public function create(#[Body] CreateMember $request): Member;
}

At a glance:

  • #[Contract], #[Get], and #[Post] define the routes.
  • #[Path] and #[Body] say where endpoint arguments come from.
  • Native parameter and return types define the request and response schemas.
  • #[Returns] adds responses outside the endpoint's normal success response.

See the compatibility profile for the complete authoring rules, including collections, nullability, defaults, security, and contract-level attributes.

Add Framework Transport

Rivet PHP does not register framework routes. A Laravel controller can use the contract DTOs while the application keeps ownership of its transport:

<?php

namespace App\Http\Controllers;

use App\Contract\CreateMember;
use App\Contract\ProblemResponse;
use App\Repository\MemberRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

final class MembersController extends Controller
{
    public function __construct(private readonly MemberRepository $members) {}

    public function show(string $id): JsonResponse
    {
        $member = $this->members->find($id);

        return $member === null
            ? response()->json(new ProblemResponse('Member not found'), 404)
            : response()->json($member);
    }

    public function store(Request $request): JsonResponse
    {
        $input = new CreateMember($request->string('email')->toString());

        return response()->json($this->members->create($input), 201);
    }
}

The repository represents application code here; routing, request mapping, and serialization remain Laravel's responsibility. See optional runtime enforcement if the host also wants Rivet to validate endpoint-to-handler registration and responses.

Inspect And Generate

Commands run from the current directory by default. --project-root changes the base for relative paths. Without contract roots or namespace filters, Rivet PHP discovers Composer PSR-4 and classmap sources automatically.

vendor/bin/rivet-php inspect \
  --contract-root src/Contract

vendor/bin/rivet-php generate \
  --contract-root src/Contract \
  --output generated

vendor/bin/rivet-php verify \
  --contract-root src/Contract \
  --output generated

Running those commands against the contract above produces:

POST    /members       Members_create
GET     /members/{id}  Members_get
Published Rivet artifacts.
Rivet artifacts are current.

Generation validates the PHP contract, generated OpenAPI, and their semantic correspondence before publishing artifacts transactionally. verify runs the same generation configuration without mutating the target and fails when the committed artifacts are stale. See Artifact lifecycle for the publication and recovery guarantees.

The committed layout is:

generated/
  api.contract.json
  openapi.json
  rivet-manifest.json

Documentation

About

PHP type reflector for Rivet — scans #[RivetType] and #[RivetRoute] annotated classes, outputs JSON contracts

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages