waffle-commons / contracts
Contracts component for Waffle framework.
Requires
- php: ^8.5
- psr/cache: ^3.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
Requires (Dev)
- carthage-software/mago: ^1.29
- cyclonedx/cyclonedx-php-composer: ^6.2
- php-mock/php-mock-phpunit: ^2.15
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-05-30 19:46:42 UTC
README
Waffle Contracts Component
Release:
v0.1.0-beta2ย |ยCHANGELOG.md
The Waffle Framework's central contract package. Every other waffle-commons/* component depends only on this package and on its declared PSR interfaces. No component may depend on a sibling's concrete implementation โ contracts is the line that keeps the ecosystem decoupled.
This package contains interfaces, attributes, enums, exception interfaces, typed constants, and the small set of concrete routing exceptions (RouteNotFoundException, MethodNotAllowedException) shared across components. No business logic ever ships from here.
๐ Beta-2 highlights โ HTTP method correctness
- NEW โ
Waffle\Commons\Contracts\Routing\Exception\MethodNotAllowedException(concretefinalclass) and itsMethodNotAllowedExceptionInterfacemarker. Carries the allowed-methods list so downstream renderers can emit the RFC 7231Allowheader on405responses. - NEW โ
Waffle\Commons\Contracts\Routing\Constant: HTTP-method string constants (METHOD_GET,METHOD_POST,METHOD_PUT,METHOD_PATCH,METHOD_DELETE,METHOD_HEAD,METHOD_OPTIONS). - CHANGED โ
Waffle\Commons\Contracts\Routing\Attribute\Route(relocated fromwaffle-commons/routingin Beta-2): gained thearray $methods = ['GET']parameter for method filtering and route overloading. Constructor argument order normalised so$pathprecedes$methods. Emptymethodsarray means "any method". - NEW โ
Waffle\Commons\Contracts\Exception\WaffleExceptionInterface: explicit base interface for the framework's exception hierarchy.
Beta-1 inheritance
The Beta-1 line introduced the security and HTTP-correctness foundations this package still carries:
- BREAKING (Beta-1) โ
CsrfTokenManagerInterface::issue(),validate(),refresh()take a$sessionIdargument. The HMAC binds tokens to the per-browserWAFFLE_SIDcookie issued byAnonymousSessionMiddleware. - NEW (Beta-1) โ
Waffle\Commons\Contracts\Security\Attribute\PublicAccessโ explicit opt-out for the fail-closed ABAC default. Without#[Voter]and without#[PublicAccess],SecureContainer::analyze()denies with HTTP403. - NEW (Beta-1) โ
Waffle\Commons\Contracts\Routing\Exception\RouteNotFoundExceptionโ concretefinalclass implementingRouteNotFoundExceptionInterface. Thrown byCoreRoutingMiddlewareso missing routes render as404instead of500. - NEW (Beta-1) โ CSRF binding constants on
Waffle\Commons\Contracts\Security\Csrf\Constant:SESSION_COOKIE_NAME(WAFFLE_SID),SESSION_ID_BYTES(32),SESSION_REQUEST_ATTRIBUTE(_anon_sid),SESSION_COOKIE_MAX_AGE(2_592_000), plusMIN_SECRET_BYTES(32) andSECRET_ENV_KEY(WAFFLE_CSRF_SECRET).
๐ฆ Installation
composer require waffle-commons/contracts
๐งฑ What's inside
| Namespace | Purpose |
|---|---|
Waffle\Commons\Contracts\Attribute |
Marker attributes (#[Dto]) consumed by the framework's argument resolver. |
Waffle\Commons\Contracts\Cache |
PSR-6 + PSR-16 extension interfaces (CacheInterface, CacheItemPoolInterface, StampedeProtectionInterface). |
Waffle\Commons\Contracts\Config |
ConfigInterface typed getters (getInt, getString, getArray, getBool). |
Waffle\Commons\Contracts\Console |
ConsoleApplicationInterface, CommandInterface, InputInterface, OutputInterface. |
Waffle\Commons\Contracts\Constant\Constant |
Ecosystem-wide typed constants (env names, security levels, attribute keys). |
Waffle\Commons\Contracts\Container |
ContainerInterface extending PSR-11 + ResettableInterface for worker-mode resets. |
Waffle\Commons\Contracts\Controller |
BaseControllerInterface. |
Waffle\Commons\Contracts\Core |
KernelInterface โ the heart of the request lifecycle. |
Waffle\Commons\Contracts\Enum |
Framework-wide enums (e.g. Failsafe). |
Waffle\Commons\Contracts\ErrorHandler |
ErrorRendererInterface for content-negotiated error rendering. |
Waffle\Commons\Contracts\EventDispatcher |
EventDispatcherInterface, ListenerProviderInterface (PSR-14 extension). |
Waffle\Commons\Contracts\Exception |
Root exception interfaces (ValidationExceptionInterface, etc.). |
Waffle\Commons\Contracts\Handler |
ArgumentResolverInterface, ResponseConverterInterface. |
Waffle\Commons\Contracts\Http |
Framework-specific HTTP factories on top of PSR-7/17 (ResponseEmitterInterface, ServerRequestFactoryInterface). |
Waffle\Commons\Contracts\Parser |
YamlParserInterface. |
Waffle\Commons\Contracts\Pipeline |
MiddlewareStackInterface (PSR-15 stack). |
Waffle\Commons\Contracts\Routing |
RouterInterface + routing exception interfaces. |
Waffle\Commons\Contracts\Runtime |
RuntimeInterface for FrankenPHP / classic SAPI bootstrap. |
Waffle\Commons\Contracts\Security |
SecurityInterface, SecurityRuleInterface, VoterInterface + #[Voter] / #[Rule] attributes + CSRF attributes. |
Waffle\Commons\Contracts\Service |
ResettableInterface โ implemented by any service that needs reset between FrankenPHP worker requests. |
Waffle\Commons\Contracts\System |
SystemInterface. |
Waffle\Commons\Contracts\Validation |
ValidatorInterface, ValidationResultInterface, ViolationInterface. |
๐ PHP 8.5 surface
The contracts package is the canonical reference for the framework's PHP 8.5 contract style:
- Typed constants everywhere โ
public const string EVENT_NAME = '...';,public const int SECURITY_LEVEL10 = 10;. - Marker attributes with
Attribute::TARGET_*declarations (#[Dto],#[Voter],#[Rule]). final readonly classfor value-object attributes.- All return types and parameter types are explicit; no
mixedin the public surface except where PSR-11 mandates it (ContainerInterface::get(): mixed).
๐ Sample interfaces
KernelInterface โ the heart of the framework:
namespace Waffle\Commons\Contracts\Core; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface KernelInterface { public function boot(): static; public function configure(): void; public function handle(ServerRequestInterface $request): ResponseInterface; public function reset(): void; }
ConfigInterface โ typed config access:
namespace Waffle\Commons\Contracts\Config; interface ConfigInterface { public function getInt(string $key, ?int $default = null): ?int; public function getString(string $key, ?string $default = null): ?string; public function getArray(string $key, ?array $default = null): ?array; public function getBool(string $key, ?bool $default = null): ?bool; }
#[Dto] โ marker attribute for auto-hydrated DTOs:
namespace Waffle\Commons\Contracts\Attribute; use Attribute; #[Attribute(Attribute::TARGET_CLASS)] final readonly class Dto {}
A DTO consumer in your controller:
use Waffle\Commons\Contracts\Attribute\Dto; use Waffle\Commons\Contracts\Exception\Validation\ValidationExceptionInterface; #[Dto] final readonly class UserLoginDto { public string $email { set(string $value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new \DomainException('Invalid email'); } $this->email = $value; } } }
๐ค Decoupling rule
Every concrete waffle-commons component must depend on waffle-commons/contracts and on nothing else from the waffle-commons/* namespace, unless explicitly declared in its own composer.json require. This rule is enforced at build time by mago guard perimeters in every component.
๐งญ Architectural boundary (mago guard)
contracts is the root of the ecosystem, so its own perimeter (enforced by vendor/bin/mago guard, bundled into composer mago, zero baselines โ see mago.toml [guard.perimeter]) is the strictest of all: production code under Waffle\Commons\Contracts may depend only on:
Waffle\Commons\Contracts\**โ itselfPsr\**โ PSR interfaces@global+Psl\**โ PHP core and the PHP Standard Library
It depends on no other waffle-commons/* package โ that is what lets every other component depend on it without creating a cycle. Test code under WaffleTests\Commons\Contracts is unrestricted (@all).
Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations (see the Decoupling rule above for the ecosystem-wide counterpart).
๐งช Testing
docker exec -w /waffle-commons/contracts waffle-dev composer tests
๐ License
MIT โ see LICENSE.md.