Data objects

Mapping arrays or JSON strings into structured classes.

As saw on the Usage page this package can map values to different types of data objects, but isn't limited to just mapping single values.

Mapping objects can sometimes become complex in special situations, this package covers some of them referenced here.

Mapping

Every feature described in the Usage page is still available on every property of a object.

In case we want to use the through method here on an object class we can use PHP Generics as the following:

use Illuminate\Support\Collection;

enum UserRole: int
{
    case Admin = 1;
    
    case Editor = 2;
    
    case Moderator = 3;
}

class UserObject
{
    /**
     * @param Collection<UserRole> $roles
     */
    public function __construct(public string $email, public Collection $roles) {}
}

map(['email' => '[email protected]', 'roles' => '1,2'])->to(UserObject::class);

Extending the example from above even further:

use App\Data\UserObject;
use Illuminate\Support\Collection;

class CreateTeamWithUsersData
{
    /**
     * @param Collection<UserObject> $users
     */
    public function __construct(public string $name, public Collection $users) {}
}

map([
    'name' => 'Developers',
    'users' => [
        ['email' => '[email protected]', 'roles' => '1,2'],
        ['email' => '[email protected]', 'roles' => '3'],
    ],
])->to(CreateTeamWithUsersData::class);

Property attributes

Inject

Injects anything that is accessible through the Laravel Container.

use App\Data\UserObject;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\Inject;

class CreateTeamWithUsersData
{
    /**
     * @param Collection<UserObject> $users
     */
    public function __construct(
        public string $name,
        public Collection $users,
        #[Inject('env')]
        public string $env,
    ) {}
}

Authenticated

This is an extension of Laravel's built-in Contextual attribute binding cause the built-in attributes doesn't work with promoted parameters.

This acts as the Inject attribute but adding the current authenticated user.

use App\Data\UserObject;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\Authenticated;

class CreateTeamWithUsersData
{
    /**
     * @param Collection<UserObject> $users
     */
    public function __construct(
        public string $name,
        public Collection $users,
        #[Authenticated]
        public ?User $currentUser = null,
    ) {}
}

Model with

Map model instances eager loading the specified relationships.

use App\Models\User;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\ModelWith;

class CreateTeamAddingExistingUsersData
{
    /**
     * @param Collection<User> $users
     */
    public function __construct(
        public string $name,
        #[ModelWith(['teams', 'ownedTeams'])]
        public Collection $users,
    ) {}
}

Resolve model

Map model instance resolving it from route param and/or property key in case there are multiple types of models accepted (using custom polymorphic names, full class otherwise):

use App\Models\User;
use App\Models\Team;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\ResolveModel;

class CreateArticleWithOwner
{
    public function __construct(
        public string $name,
        public string $content,
        #[ResolveModel(morphTypeFrom: 'owner_type')]
        public User|Team $owner,
        public string $ownerType,
    ) {}
}

Class attributes

As type

This is used for TypeScript code generation which change the type name when the TypeScript code is generated:

use OpenSoutheners\LaravelDataMapper\Attributes\AsType;

#[AsType('CreateUserForm')]
class UserObject
{
    /**
     * @param Collection<UserRole> $roles
     */
    public function __construct(public string $email, public Collection $roles) {}
}

This can be used anywhere in any class that is exportable to TypeScript code.

Normalise properties

By default properties are being normalised so user_id becomes user or user_role to userRole although this seems pretty opinionated it improves the readability of our objects.

This can be also configured globally (also disabled) through the package config file that may be published following the Quickstart page guide.

So in case we disabled normalise properties globally we could enable selectively using the attribute:

use App\Models\User;
use App\Models\Team;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\ResolveModel;
use OpenSoutheners\LaravelDataMapper\Attributes\NormaliseProperties;

#[NormaliseProperties]
class CreateArticleWithOwner
{
    public function __construct(
        public string $name,
        public string $content,
        #[ResolveModel(morphTypeFrom: 'owner_type')]
        public User|Team $owner,
        public string $ownerType,
    ) {}
}

Usage in controllers

Mapping these data directly to objects in PHP is very useful but this package also has some functionality so these objects can be directly used in our Laravel application controllers:

use App\Models\User;
use App\Models\Team;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDataMapper\Attributes\ResolveModel;
use OpenSoutheners\LaravelDataMapper\Contracts\RouteTransferableObject;

class CreateArticleWithOwner implements RouteTransferableObject
{
    public function __construct(
        public string $name,
        public string $content,
        #[ResolveModel(morphTypeFrom: 'owner_type')]
        public User|Team $owner,
        public string $ownerType,
    ) {}
}

class ArticleController
{
    public function store(CreateArticleWithOwner $data)
    {
        // Your controller logic
    }
}

Validating

When they're injected into the controllers we loss the ability to validate the data that users are sending into these objects, so that's why we can use the Validate class attribute:

use Illuminate\Foundation\Http\FormRequest;
use OpenSoutheners\LaravelDataMapper\Attributes\Validate;

class CreateArticleWithOwnerFormRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'name' => ['required', 'string', 'max:250'],
            'content' => ['required', 'string', 'min:120'],
            'owner_id' => ['required', 'numeric'],
            'owner_type' => ['required', 'string'],
        ];
    }
}

#[Validate(CreateArticleWithOwnerFormRequest::class)]
class CreateArticleWithOwner
{
    public function __construct(
        public string $name,
        public string $content,
        #[ResolveModel(morphTypeFrom: 'owner_type')]
        public User|Team $owner,
        public string $ownerType,
    ) {}
}

class ArticleController
{
    public function store(CreateArticleWithOwner $data)
    {
        // Your controller logic with validated data
    }
}

Last updated

Was this helpful?