Laravel Integration
Laravel-specific features for Eloquent models and HTTP responses. The core Data
class stays framework-agnostic — these APIs light up when Illuminate packages are
present. See Framework-agnostic usage.
Hydrating from models
Data::from($model) / fromModel() reads Eloquent attributes (and relations when
loaded). Combine with name mapping when attribute
names differ from PHP properties.
$user = UserData::from(User::find(1));
$user = UserData::fromModel($model);
Conditional hydration attributes
These live under BradieTilley\Data\Attributes\Laravel and mirror common
JsonResource helpers (whenLoaded, whenCounted, whenAppended, when).
They are evaluated only when hydrating from an Eloquent model
(Data::from($model) / fromModel()). Array, JSON, and other inputs ignore
them and hydrate normally.
Type conditional properties as Sometimes|T so a failed condition becomes an
omitted key in toArray() (the same role as JsonResource’s MissingValue). See
Sometimes.
| Attribute | Included when… | Value source |
|---|---|---|
WhenLoaded('roles') | $model->relationLoaded('roles') | $model->getRelation('roles') (never lazy-loads) |
WhenCounted('views') / WhenCounted('postViews') | snake {relation}_count is on attributes, or the relation is loaded | e.g. views_count, post_views_count |
WhenAppended('url') | $model->hasAppended('url') | $model->getAttribute('url') |
When(IsAuthenticated::class) | condition callable returns true | normal model attribute via the property / MapName input key |
Condition signature (When): (Model $model): bool
using signature (optional on all four): (mixed $value, Model $model): mixed
use BradieTilley\Data\Attributes\Laravel\When;
use BradieTilley\Data\Attributes\Laravel\WhenAppended;
use BradieTilley\Data\Attributes\Laravel\WhenCounted;
use BradieTilley\Data\Attributes\Laravel\WhenLoaded;
use BradieTilley\Data\Attributes\Sometimes;
class PostData extends Data
{
public function __construct(
public string $title,
#[WhenLoaded('user')]
public Sometimes|UserData $user,
#[WhenLoaded('roles', using: RoleNames::class)]
public Sometimes|array $roleNames,
#[WhenLoaded('roles', using: [RolePresenter::class, 'names'])]
public Sometimes|array $roleLabels,
#[WhenCounted('views')]
public Sometimes|int $viewsCount,
#[WhenAppended('url')]
public Sometimes|string $url,
#[When(IsAuthenticated::class)]
public Sometimes|string $email,
) {}
}
Attribute callables
Closures cannot be used in PHP attributes (attribute arguments must be constant
expressions). For When conditions and optional using: transforms, pass:
- an invokable class-string —
RoleNames::classwhereRoleNamesimplements__invoke(...) - a
[ClassName, 'method']pair — static or instance methods are supported
final class RoleNames
{
public function __invoke(mixed $value, Model $model): array
{
return $value->pluck('name')->all();
}
}
final class RolePresenter
{
public static function names(mixed $value, Model $model): array
{
return $value->pluck('label')->all();
}
}
Callables are resolved by AttributeCallable:
- Instantiated with no constructor arguments — there is no service container
- Invokable classes must be callable via
__invoke [Class, 'method']uses a static call when the method is static; otherwise a fresh instance is created and the method is called
Responses
Every Data object implements JsonSerializable. On Laravel, return a Data object
straight from a controller by adding the ResponsableData trait (and the matching
interfaces):
use BradieTilley\Data\Concerns\ResponsableData;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Responsable;
class ProductData extends Data implements Arrayable, Responsable
{
use ResponsableData;
// ...
}
// In a controller — returns a JsonResponse:
return ProductData::from($product);
Or extend BradieTilley\Data\LaravelData instead of Data — it already
implements Arrayable + Responsable via the trait:
use BradieTilley\Data\LaravelData;
class ProductData extends LaravelData
{
// ...
}
The trait/base are opt-in so the framework-agnostic Data core never references
Illuminate contracts.
For API envelopes and paginator wrappers, see Predefined DTOs. For wrapping and partials, see Serialization.