A simple and lightweight implementation of Data Transfer Objects (DTO) in Laravel with optional casting support.
Under the hood it implements Laravel's Castable
interface with a Laravel custom cast that handles serializing between the DataTransferObject
(or a compatible array) and your JSON database column.
You can install the package via composer:
composer require tailflow/dto
namespace App\DataTransferObjects;
use Tailflow\DataTransferObjects\DataTransferObject;
class Address extends DataTransferObject
{
public string $country;
public string $city;
public string $street;
}
namespace App\DataTransferObjects;
use Tailflow\DataTransferObjects\DataTransferObjectCollection;
class WorkAddresses extends DataTransferObjectCollection
{
public static function getItemClass(): string
{
return Address::class;
}
}
Note that this should be a jsonb
or json
column in your database schema.
namespace App\Models;
use App\DataTansferObjects\Address;
use App\DataTansferObjects\WorkAddresses;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $casts = [
'address' => Address::class,
'work_addresses' => WorkAddresses::class,
];
}
Pass DTOs as arguments or use as return values, and get a nice autocompletion.
function getAddress(Address $originalAddress): Address
{
$address = new Address();
$address->county = $originalAddress->country;
$address->city = 'Tokyo';
$address->street = '4-2-8 Shiba-koen';
return $address;
}
// or
function getAddress(): Address
{
return new Address(
[
'country' => 'Japan',
'city' => 'Tokyo',
'street' => '4-2-8 Shiba-koen',
]
);
}
On Eloquent models, you can now pass either an instance of your Address
class, or even just an array with a compatible structure. It will automatically be cast between your class and JSON for storage and the data will be validated on the way in and out.
$workAddress = new Address();
$workAddress->country = 'Japan';
$workAddress->city = 'Osaka';
$user = User::create([
// ...
'address' => [
'country' => 'Japan',
'city' => 'Tokyo',
'street' => '4-2-8 Shiba-koen',
],
'work_addresses' => [
$workAddress
]
]);
$residents = User::where('address->city', 'Tokyo')->get();
But the best part is that you can decorate your class with domain-specific methods to turn it into a powerful value object.
$user->address->toMapUrl();
$user->address->getCoordinates();
$user->address->getPostageCost($sender);
$user->address->calculateDistance($otherUser->address);
echo (string) $user->address;
The MIT License (MIT). Please see License File for more information.