andydefer/domain-structures
A Laravel/PHP package that provides reusable domain structures and building blocks for domain-driven design. It helps organize business logic, reduce boilerplate, and keep core rules isolated from framework and infrastructure concerns for cleaner, more maintainable applications.
The package targets a common Laravel pain point: unstructured arrays crossing layer boundaries. Before writing code, note the maturity signal — 0 stars, 0 dependents, and no ecosystem footprint — so treat the repo's README.md, src/, and especially tests/ as the source of truth rather than assuming API parity with Spatie Data or hand-rolled VOs.
Install with a pinned version. If no tagged release exists:
composer require andydefer/domain-structures:dev-main#<commit-sha>
Check composer.json for extra.laravel.providers (auto-discovery) or register the service provider manually in bootstrap/providers.php.
Read the tests first. For a low-adoption package, tests are the real documentation — scan for base classes covering records, value objects, DTOs, and typed collections, and note whether they are abstract or require specific methods.
Start with one use case: replace a validated request array with a DTO in a controller/action.
Illustrative shape (confirm exact base class names in the repo):
final class CreateUserData extends Data
{
public function __construct(
public readonly string $name,
public readonly string $email,
) {}
}
public function store(CreateUserRequest $request, CreateUser $action)
{
$data = CreateUserData::fromArray($request->validated());
$action->handle($data);
}
If the package doesn't fit after a 30-minute spike on a single endpoint, stop. At this maturity level the switching cost is low.
DTOs at the boundaries. Use them between HTTP/queue/console input and domain services. Keep fromArray/fromRequest mapping in one place, and keep DTOs plain — do not extend Eloquent models.
Value objects for invariants. Email, Money, Phone, OrderId, DateRange. Construct through named constructors (Email::fromString) so validation lives in one place, and add equals() rather than comparing objects with == or ===.
Typed collections for domain lists. Return UserCollection from repositories instead of a bare Collection. Enforce the item type at insertion, and add domain methods (total(), ids(), pluckEmail()) so callers don't reach for raw arrays.
Records for read models. Use records for query projections — dashboard rows, report lines, export rows — where you need shape and type safety but not behavior. Hydrate from DB::select or ->get()->map(...).
Eloquent casts. This is the highest-leverage integration point: wrap a VO in a custom cast so $user->email returns an Email instance and assignment accepts string|Email. Write one cast per VO and reuse it across models.
Serialization contract. Any DTO used in jobs, events, or cache must round-trip. Implement JsonSerializable/__serialize, or use the package's toArray. Test with dispatchSync before switching to a real queue.
Testing split. Unit-test VOs and typed collections with data providers; test DTO mapping separately from persistence. Use model factories only for Eloquent-backed tests.
Wrap it. Put the package behind your own interfaces (e.g., a DataMapper contract) so swapping to spatie/laravel-data later is a localized change.
readonly/no-setter designs require with... clone patterns for "modify" flows. Watch for Laravel internals or packages that expect mutable public properties.instanceof or exact class.$model->email access may construct a new VO. Use equals(); never ===.InvalidArgumentException. Catch these at the boundary and rethrow as ValidationException so users see field errors, not 500s.php artisan vendor:publish), and verify namespaces — small packages often change them between 0.x commits.dd(get_class($item), $item) at the insertion point. When DTO mapping fails, log the raw payload before the mapping call.toArray), but keep inheritance shallow; prefer traits where supported.spatie/laravel-data or hand-rolled VOs remains a one-file change.How can I help you explore Laravel packages today?