Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Contract Laravel Package

rapira/contract

Shared contracts and interfaces for the Rapira Laravel ecosystem. rapira/contract provides stable abstractions that keep packages decoupled and make it easier to build extensible, maintainable applications. Depend on contracts instead of concrete implementations for cleaner, more portable code.

View on GitHub
Deep Wiki
Context7

Getting Started

rapira/contract is the PHP-side type contract layer for Rapira (the rapira-rs org implies a Rust core with a PHP binding). Treat it as a shared vocabulary package: interfaces, DTOs, enums, exception types, and a set of stub functions that mirror the Rust runtime's entry points. It is not a framework and not a full SDK.

Note the actual version line: the latest release is 0.9.1, not a 1.x. The package is pre-1.0, so minor bumps are permitted to break you.

Minimal steps:

  1. composer require rapira/contract — pinned to an exact version ("0.9.1"), not ^0.9. Then immediately open vendor/rapira/contract/composer.json. With 1 star and 0 dependents there is effectively no external documentation, so the manifest is your spec: check require for ext-rapira/ext-* entries, the PHP version floor, the PSR-4 namespace root, and the autoload.files entry — the stub functions likely live in a separately included functions.php, which matters for collisions and for whether they can be swapped.
  2. Read the source tree end-to-end. It is small enough for one sitting. Classify into five buckets now, not four: interfaces you implement, interfaces you consume, enums/value objects you construct, exceptions you catch, and functions you call (the stubs).
  3. Find the runtime double. PR #6 ("let the stub functions answer through a runtime double") is the headline feature of 0.9.1. Look for a testing namespace (Rapira\Contract\Testing\*), a Runtime/Double/Fake class with a setter or static swap(), or an env-var switch read at function-call time. Confirm whether it ships in require or require-dev, and whether it is autoloaded in production.
  4. First use case (new, and cheaper than the integration path): write a Pest/PHPUnit test that calls a stub function through the double and asserts on the value it returned / the calls it recorded. If that works without ext-rapira installed, you have a validated testing seam on day one — this is the single most valuable thing in the package right now.
  5. Second use case: take one existing endpoint that passes associative arrays around and re-type it. Have the handler accept a contract DTO and return a contract response type. This surfaces whether the package is actually wired into the runtime or is a standalone set of shapes plus unconnected function stubs.

If step 5 works with zero glue code, the contracts are enforced by the Rapira runtime. If it doesn't, you have a plain PHP library plus testable stubs — still useful, but plan accordingly.

Implementation Patterns

Stub functions are your seam — use them only in tests. The 0.9.1 double is a testing primitive, not a production runtime. Wire it in a base TestCase:

protected function setUp(): void
{
    parent::setUp();
    // exact API depends on the package — inspect the double class first
    ContractDouble::register();   // e.g. a global/static swap consumed by the stubs
}

If the double exposes call recording (most do), assert on it rather than on fabricated return values: assertCalled('rapira_invoke', $dto) catches argument-shape regressions, whereas a canned return value does not.

Interface-first handlers. Implement the contract interfaces on your own application classes rather than importing concrete Rapira types. The runtime binds by interface, so handlers stay free of vendor concrete classes and remain unit-testable even without the double.

Boundary adapter layer. Do not let contract types leak into your domain:

HTTP Request → FormRequest (Laravel validation)
             → Contract DTO (rapira/contract)
             → Domain object (yours)
             → Contract Response DTO
             → JSON

The mapping layer is ~20 lines but means an abandoned package costs you a day, not a rewrite.

Enum-driven error handling. Map contract error/status enums once in a single App\Exceptions\Handler::render rather than switching at every call site. Use a match with no default arm so a new enum case from a 0.x upgrade becomes a loud failure instead of a silent 500.

Laravel service provider binding. Register contract→implementation pairs in a dedicated provider:

$this->app->bind(SomeContract::class, AppSomeAdapter::class);

Keep this small and explicit. Do not auto-scan vendor/rapira/contract for implementations — it defines shapes and stubs, you supply behaviour. Register the runtime double in a test-only provider or in TestCase::setUp, never in AppServiceProvider.

Fakes over mocks, still. Where the package's own double doesn't cover something (e.g. an interface you implement that the double can't satisfy), hand-write fakes in tests/Fakes/. They double as documentation of the expected shape. Prefer the shipped double where it exists — it will track signature changes for you on upgrade.

Pin hard in CI. Commit composer.lock, use composer install --no-dev --prefer-dist in CI, and add a job running the suite against the minimum and maximum allowed PHP versions. For a package this thin the risk is not runtime bugs — it is silent signature changes. At 0.9.x that risk is structural: 0.9 → 0.10 may legally break function signatures, return types, and the double's API.

Composer provide/virtual package trick. If you need a fallback, declare "provide": {"rapira/contract": "self.version"} in an internal package so downstream code can type-hint the contract namespace regardless of which implementation is installed. Note the namespace now covers functions as well as types — a fallback implementation must ship the same function names or the stubs will fatal on include.

Gotchas and Tips

The double can silently mask a broken integration. Once ContractDouble::register() is in your base TestCase, every test passes whether or not Rapira is installed, whether or not ext-rapira loads, and whether or not the real runtime agrees with your DTOs. Keep at least one non-doubled integration test (or a CI job without the double registered) that exercises the real extension — otherwise 0.9.1 makes your suite greener and your confidence lower.

Guard against production registration. If the swap is a global/static setter rather than a container binding, it is trivially reachable from production code. Confirm the double class is excluded from the production autoloader (or add a class_exists() guard + an app()->environment('testing') assertion in the registration path). A stub answering from a fake in production returns fabricated data with no exception.

Stub function behaviour without the double. Without ext-rapira and without the double registered, the stubs likely throw, return null, or return an empty DTO. That is a silent-wrong-answer class of bug, not a loud one. Verify explicitly:

php -m | grep -i rapira
composer show rapira/contract
composer check-platform-reqs

Then call one stub in php artisan tinker and inspect the actual outcome before building on it.

Verify the native requirement. composer install succeeding does not mean Rapira is installed. If composer.json lists an ext-* requirement and your platform lacks it, Composer either hard-fails or — worse — your config.platform overrides mask it.

Pre-1.0 versioning is the real hazard. 0.9.1 is a minor bump containing a feature that changes how the package is consumed; expect the same again. Read the git tags and CHANGELOG.md directly rather than trusting Packagist metadata (the release metadata has looked anomalous before, so automated currency checks are unreliable). Prefer an exact constraint or a commit hash over ^0.9.

Check how the double is toggled. Three plausible designs, each with a different failure mode: a static setter (leaks across tests — reset in tearDown), a container binding (safe, but only if the stubs resolve through the container, which functions generally cannot), or a global flag read per call (cheap, but invisible in stack traces). Read the implementation before writing 200 tests against the wrong assumption.

Function stubs are not mockable by interface. You cannot bind() a namespaced function. If your handlers call stub functions directly, those call sites are hard to isolate. Wrap each stub in a thin injectable class (App\Rapira\Runtime) that implements your own interface, and call the stub from exactly one place. This also gives you a production/testing swap that doesn't depend on the package's double at all.

Autoload files collisions. autoload.files entries run on every request. Run composer dump-autoload -o and check for duplicate function names if the contract namespace is broad (e.g. Rapira\) or if you maintain a local fork. Fix with PSR-4/file exclusions in the root composer.json, not by editing vendor files. If composer.json declares "files", remember those functions cannot be conditionally autoloaded per environment without Composer tricks.

Type mismatches surface as bare TypeError. There is no runtime validation layer here — a contract violation is a PHP fatal, not a friendly 422. Mitigate with:

  • declare(strict_types=1); in every file touching contracts (without it, scalar coercion hides bugs until production).
  • A quick introspection dump during integration: print_r(class_implements($obj)); and (new ReflectionClass($dto::class))->getConstructor()->getParameters().
  • A global error handler converting contract TypeErrors into a 500 with the class path logged — otherwise you debug via blank responses.

Check for final, readonly, and private constructors. Many DTO packages mark classes final with private constructors plus static named constructors (::fromArray(), ::fromRequest()). You cannot extend them — decorate instead, and always go through the named constructor rather than new. The same likely applies to the double class; don't subclass it to add assertions, wrap it.

Do not serialize contract objects directly to your public API. They are internal transport shapes; a 0.x patch could rename a property and break your JSON contract with clients. Always map to your own response classes or resources.

Extension points are thin by design — with one new exception. The intended extension mechanism is implementing the interfaces plus, as of 0.9.1, swapping the runtime double in tests. Everything else belongs in the adapter layer. That is correct, not a workaround.

Escape hatch. The types are interface declarations and the behaviour is a handful of functions. If the package stalls, copy the interfaces and the function signatures into your codebase, keep the same FQCNs and function names via a classmap / provide declaration, and remove the dependency. 0.9.1 makes this slightly more attractive: you now have a documented seam where your own fake can stand in for the stubs, so the fork is testable immediately. Budget half a day.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core