spatie/laravel-data
Define rich, typed data objects once and use them for requests, validation, API resources/transformers, and TypeScript definitions. Create from arrays/requests/models, apply rules automatically, and transform only what’s needed with lazy properties.
Installation:
composer require spatie/laravel-data
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\LaravelData\LaravelDataServiceProvider" --tag="config"
Define a Data Object:
Create a class extending Spatie\LaravelData\Data in app/Data (or your configured directory):
namespace App\Data;
use Spatie\LaravelData\Data;
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {}
}
First Use Case:
Create a SongData instance from an array:
$songData = SongData::from([
'title' => 'Bohemian Rhapsody',
'artist' => 'Queen',
]);
Request Validation & Transformation:
Replace FormRequests with Data objects for validation and transformation:
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation;
class CreateSongRequest extends Data
{
public function __construct(
#[Validation('required|string|max:255')]
public string $title,
#[Validation('required|string')]
public string $artist,
) {}
}
// In controller:
$requestData = CreateSongRequest::from($request->all());
API Responses:
Use Data as API resources (no need for separate transformers):
return response()->json(SongData::from($song));
Model Integration:
Attach Data to Eloquent models for type-safe serialization:
use Spatie\LaravelData\WithData;
class Song extends Model
{
use WithData;
protected $dataClass = SongData::class;
}
// Usage:
$songData = Song::find(1)->getData();
TypeScript Generation: Generate frontend types via CLI:
php artisan data:generate-typescript
Lazy Properties: Load nested data on-demand:
class AlbumData extends Data
{
public function __construct(
public string $name,
public ?SongData $featuredSong = null,
) {}
public function getFeaturedSong(): ?SongData
{
return $this->featuredSong ?? SongData::from([
'title' => 'Lazy Loaded',
'artist' => 'Dynamic',
]);
}
}
Custom Casts:
Transform input values (e.g., strings to Carbon):
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Attributes\WithCast;
class DateCast implements Cast
{
public function cast($value): \Carbon\Carbon
{
return \Carbon\Carbon::parse($value);
}
}
class EventData extends Data
{
public function __construct(
#[WithCast(DateCast::class)]
public string $date,
) {}
}
Nested Data:
Handle arrays/collections of Data:
class PlaylistData extends Data
{
public function __construct(
public array $songs, // Automatically casts to SongData[]
) {}
}
Reflection Overhead:
php artisan data:cache-structures
config/data.php:
'structure_caching' => [
'cache' => ['store' => 'redis'],
],
Null Handling:
null (use magic methods or default values):
public function __construct(
public ?string $optionalField = null,
) {}
Type Mismatches:
#[Validation] for runtime checks:
#[Validation('nullable|string')]
public ?string $field;
Circular References:
UserData ↔ ProfileData) to prevent infinite loops.Inspect Data Structure:
Use dd($data->toArray()) or dd($data->getProperty('field')) to debug properties.
Validation Errors:
Catch ValidationException for detailed error messages:
try {
$data = SongData::from($request->all());
} catch (ValidationException $e) {
return response()->json($e->errors(), 422);
}
Performance Profiling: Disable caching temporarily to measure reflection impact:
'structure_caching' => ['enabled' => false],
Custom Validation:
Extend ValidationStrategy for reusable rules:
use Spatie\LaravelData\Support\Validation\ValidationStrategy;
class CustomValidationStrategy extends ValidationStrategy
{
protected function validateProperty($value, $rule): void
{
// Custom logic
}
}
Dynamic Properties: Use magic methods for runtime properties:
public function getFullTitle(): string
{
return "{$this->artist} - {$this->title}";
}
Global Casts:
Register casts globally in AppServiceProvider:
use Spatie\LaravelData\Casts\Cast;
public function boot()
{
Data::macro('castUsing', function (Cast $cast) {
Data::addGlobalCast($cast);
});
}
TypeScript Customization:
Override generateTypescript in Data:
public static function generateTypescript(): string
{
return "export interface CustomSongData { ... }";
}
How can I help you explore Laravel packages today?