charcoal-dev/contracts
Lightweight set of PHP/Laravel contract interfaces for the Charcoal ecosystem. Defines shared abstractions to keep packages decoupled and consistent, making it easier to swap implementations, test components, and build integrations across projects.
Installation Add the package via Composer:
composer require charcoal-dev/contracts
No configuration is required—this is a pure contract package with zero runtime dependencies.
First Use Case: Defining a Custom Contract
Extend the base Charcoal\Contracts\Foundation\FoundationContract or use standalone interfaces:
use Charcoal\Contracts\Foundation\FoundationContract;
class MyService implements FoundationContract
{
public function __invoke(): void
{
// Implementation
}
}
Where to Look First
src/Contracts/: Core interfaces (e.g., FoundationContract, ServiceContract).src/Traits/: Optional reusable traits (e.g., HasDependencies).README.md: Framework-agnostic design principles (if available).Use contracts to enforce DI-compatible interfaces:
use Charcoal\Contracts\Service\ServiceContract;
class UserRepository implements ServiceContract
{
public function find(int $id): ?UserModel
{
// ...
}
}
bind() or tag() in AppServiceProvider:
$this->app->bind(UserRepository::class, function ($app) {
return new UserRepository($app->make(Database::class));
});
Organize contracts by domain (e.g., Auth/, Payment/):
app/Contracts/
├── Auth/
│ ├── AuthenticatorContract.php
│ └── TokenManagerContract.php
└── Payment/
└── GatewayContract.php
Mock contracts in unit tests:
$this->mock(ServiceContract::class)
->shouldReceive('execute')
->once();
Use provided traits for common patterns:
use Charcoal\Contracts\Traits\HasDependencies;
class LoggerService implements ServiceContract
{
use HasDependencies;
protected $dependencies = [LoggerInterface::class];
}
No Runtime Behavior Contracts are interfaces only—implementations must be provided elsewhere (e.g., Laravel services). Avoid expecting magic methods or default logic.
Namespace Collisions
If extending Laravel’s built-in contracts (e.g., Illuminate\Contracts\Auth\Authenticatable), prefix your contracts (e.g., App\Contracts\Auth\Authenticatable).
Over-Engineering Resist creating contracts for trivial classes. Focus on abstraction boundaries (e.g., repositories, gateways).
php artisan tinker to verify bound services:
$this->app->make(UserRepository::class); // Should resolve without errors.
declare(strict_types=1)) and IDE hints (PHPStorm/VsCode) to catch unimplemented methods early.Custom Contracts
Extend FoundationContract for framework-specific needs:
namespace App\Contracts;
use Charcoal\Contracts\Foundation\FoundationContract;
interface QueueWorkerContract extends FoundationContract
{
public function process(string $payload): void;
}
Dynamic Binding
Use Laravel’s when() for conditional contract resolution:
$this->app->when(ServiceContract::class)
->needs(LoggerInterface::class)
->give(function ($app) {
return new FileLogger();
});
Documentation
Add PHPDoc @method annotations for IDE autocompletion:
/**
* @method void execute()
*/
interface ServiceContract {}
How can I help you explore Laravel packages today?