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

Contracts Laravel Package

dragon-code/contracts

Dragon Code Contracts provides a set of reusable PHP interfaces for any project, helping standardize implementations and keep code decoupled. Lightweight, framework-agnostic, and easy to install via Composer.

View on GitHub
Deep Wiki
Context7

Getting Started

dragon-code/contracts is an interfaces-only package — installing it gives you zero runtime behaviour, only type definitions. Installation is a single command:

composer require dragon-code/contracts

There is no service provider, no config file, no publishable assets. The first thing to do after install is read the source directly, because the README is essentially a licence statement:

ls vendor/dragon-code/contracts/src

You'll find a folder-per-domain layout roughly along these lines (names have drifted between releases, so verify against the installed version):

  • Cache/ — cache store contracts, a Ttl contract, key-existence and rememberForever-style methods
  • Queue/ — job-related contracts such as ShouldQueue, ShouldBeUnique
  • Http/ — builder contracts including getBaseDomain() / getDomainLevel()
  • Dtoable — a marker/contract for objects that can be serialised to an array or DTO
  • Actions/ — a contract for single-purpose action classes
  • Database/ (or similar) — migration-related contracts

Your first use case should be the smallest possible win: pick one class you already have and make it implement a contract that describes what it does today. The most common entry point is a DTO:

use DragonCode\Contracts\Dtoable;

class UserDto implements Dtoable
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
    ) {}

    // match the exact signature the installed interface declares
    public function toArray(): array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }
}

Then grep for implements in your own codebase to see where you can start type-hinting against the interface instead of the concrete class. That's the whole value proposition in one motion: swap the concrete class, keep the call site.

Implementation Patterns

1. Type-hint the contract, bind the implementation. The single most useful day-to-day pattern is constructor injection against interfaces, resolved through a service provider:

use DragonCode\Contracts\Cache\Store;

class ReportRepository
{
    public function __construct(private Store $cache) {}
}

// app/Providers/AppServiceProvider.php
$this->app->bind(Store::class, RedisStoreAdapter::class);

This is what makes the package worth having: your domain code stops depending on Illuminate\Cache\Repository or Redis directly, and your tests can bind an in-memory fake.

2. Actions as first-class units. Implement the action contract on every single-purpose class (SendWelcomeEmail, RecalculateInvoice). You get a consistent shape, and you can build one dispatcher/bus that accepts anything satisfying the contract rather than switching on class names.

3. Cache TTL objects. Rather than scattering now()->addMinutes(15) through the codebase, implement the Cache\Ttl contract on small value objects (FiveMinutes, OneDay). Your cache layer then accepts Ttl and resolves the integer internally — much easier to change globally later.

4. DTOs behind Dtoable. If you're on DragonCode's wider ecosystem (dragon-code/helpers, dragon-code/support), Dtoable is the integration seam — implement it and their serialisation helpers pick it up. If you're not on that ecosystem, treat it as a plain marker for "this object knows how to flatten itself," and put the actual casting logic in your own layer.

5. HTTP domain helpers for multi-tenant / subdomain routing. Contracts exposing getBaseDomain() and getDomainLevel() let you normalise api.eu.example.com vs example.com behind one interface. Useful when your tenancy resolver and your URL builder need to agree on what "the base domain" means without sharing an implementation.

6. Adapter pattern for third-party SDKs. Wrap Stripe/Bitbucket/mail drivers in a class implementing the relevant contract, then inject the contract everywhere. Swapping providers becomes a one-line binding change. The release notes mention queue-splitting by Cashier driver — that's this pattern in action.

7. Testing. Because everything is an interface, this is the cleanest use: $this->mock(Store::class) or a hand-written InMemoryCacheStore implements Store. Prefer the hand-written fake for domain tests; use mocks when you're asserting on call order.

Gotchas and Tips

⚠️ The repo is archived. No further fixes will land. Treat the installed version as frozen, vendor it into your own namespace if it's load-bearing, and don't advertise it as a maintained dependency in a public package's composer.json. With zero dependents and a tiny star count, you're also the only person who will find the bugs.

⚠️ Name collisions with Laravel are the trap. The package ships its own ShouldQueue and ShouldBeUnique. Laravel's dispatcher checks instanceof Illuminate\Contracts\Queue\ShouldQueue and Illuminate\Contracts\Queue\ShouldBeUnique — not these. If you implement the DragonCode version instead:

  • jobs marked ShouldQueue will execute synchronously, silently;
  • unique-job locking simply won't happen, and you'll get duplicate execution under load.

Always alias imports explicitly and triple-check which ShouldQueue a job implements:

use Illuminate\Contracts\Queue\ShouldQueue; // if you want Laravel's queue behaviour

Signature fidelity matters more than usual. These are bare interfaces with no traits or abstract classes to soften mismatches. If you implement a Cache Store contract, your get()/put()/has()/doesntHave()/rememberForever() parameter types, defaults, and return types must match the installed version exactly — including nullability and void vs. fluent returns. PHP will only tell you at fatal-error time, so wire up PHPStan or Psalm at level 5+ and let it catch drift.

Minor versions have added methods to existing interfaces. rememberForever and doesntHave were both bolted onto the Cache contract in 2.x. Adding a method to an interface is a breaking change for every implementer. Pin tightly ("dragon-code/contracts": "~2.25.0"), and run composer update deliberately rather than as part of routine maintenance — check the release notes for a given interface before bumping.

There are no implementations, and that's not a bug. Don't go looking for a Redis driver, a migration runner, or a working queue decorator. The package is deliberately hollow. If you want batteries, you want DragonCode's sibling packages or Laravel itself.

Laravel/Symfony version coupling is real despite the abstraction. The package tracks Laravel 11/12 and Symfony 6/7 in its own composer.json. On Laravel 10 or older you'll hit dependency resolution conflicts even though nothing in the interfaces is framework-specific. If you must support older versions, fork and strip the framework constraints — the interfaces themselves are largely portable to any PHP 8 project.

Debugging implementer mismatches. When PHP throws Class ... must implement interface ..., the message usually names the concrete method, not the interface. grep -rn "implements .*Store" src/ plus php -l on the offending file gets you there faster than reading stack traces. For runtime "why isn't my unique lock working" bugs, class_implements($job) at dispatch time is the fastest check.

Extension points. The clean pattern is to define your interface extending theirs, then depend on yours:

interface TenantCache extends \DragonCode\Contracts\Cache\Store
{
    public function forTenant(string $tenant): static;
}

This keeps third-party contracts at the boundary and gives you a place to evolve without waiting on an archived repo. Never let upstream interfaces leak into your public API — wrap them at your package's edge so a future replacement (or a Laravel-native contract) is a local change.

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.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus