DEV Community

Alexandr
Alexandr

Posted on

Aliaser – elegant aliases for models, Livewire and DTO in Laravel

Aliaser – elegant aliases for models, Livewire and DTO in Laravel

In large Laravel projects, class names inevitably become long: App\Domain\Orders\Aggregates\OrderAggregate, App\Livewire\Forms\Checkout\ShippingForm, App\ValueObjects\Money. This is tolerable in code, but as soon as such objects get into a Livewire snapshot or polymorphic relationships, they begin to shine the internal architecture outwards and inflate the HTML.

I wanted to get two things at the same time:

  • short, clear aliases instead of long FQCNs;
  • keep all the comfort of working with models, collections, forms and value objects.

From this desire, the Aliaser package was born.


What does Aliaser do?

The idea is simple: Instead of using full class names everywhere, the application works with short aliases.:

  • user instead of App\Models\User
  • postForm instead of `App\Livewire\Forms\PostForm'
  • money instead of App\ValueObjects\Money
  • UserStatus instead of `App\Enums\UserStatus'
  • etc.

The package solves several tasks:

  • gives an Entity facade for working with models by aliases: Entity::user()->where('active', true)->get();
  • registers aliases for models, forms, DTO/VO, collections, and enums in separate registries;
  • automatically replaces FQCN with aliases in Livewire snapshots;
  • can synchronize aliases of models with Eloquent morph map so that short values are stored in the database.

Quick start

Installation:

composer require sindyko/aliaser
php artisan aliaser:install
Enter fullscreen mode Exit fullscreen mode

After installation, the config will appear config/aliaser.php, as well as several auxiliary functions for registering aliases.

Registering aliases in AppServiceProvider

The entire correspondence map can be described in App\Providers\AppServiceProvider::boot():

use App\Models\{User, Post, Comment};
use App\Livewire\Forms\{PostForm, UserForm};
use App\ValueObjects\Money;
use App\DTOs\UserFilterDTO;
use App\Enums\{UserStatus, UserRole};
use App\Collections\UserCollection;

public function boot(): void
{
    // Models
    modelsMap([
        'user'    => User::class,
        'post'    => Post::class,
        'comment' => Comment::class,
    ]);

    // Livewire‑forms
    formsMap([
        'postForm' => PostForm::class,
        'userForm' => UserForm::class,
    ]);

    // DTO and value‑objects
    objectsMap([
        'money'      => Money::class,
        'userFilter' => UserFilterDTO::class,
    ]);

    // Collections
    collectionsMap([
        'userCollection' => UserCollection::class,
    ]);

    // Enums
    enumsMap([
        'userStatus' => UserStatus::class,
        'userRole'   => UserRole::class,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Entity facade: working with models by aliases

The Entity facade allows you to access models by their alias. If you call the method without arguments, it returns a proxy object that behaves like a query builder. If you pass an ID (or an array of IDs), a search will occur.

use Entity;

// Query builder
$users = Entity::user()
    ->where('active', true)
    ->latest()
    ->paginate();

// Search by ID
$user  = Entity::user(1);
$posts = Entity::post([1, 2, 3]);

// ith a selection of columns
$user = Entity::user(1, ['id', 'name', 'email']);
Enter fullscreen mode Exit fullscreen mode

A proxy can also throw static model methods.:

// Static methods
Entity::user()->create([...]);
Entity::user()->whereEmail('test@example.com')->first();
Entity::user()->isSoftDeletable();
Enter fullscreen mode Exit fullscreen mode

Inside, a small ModelProxy is used, which determines whether the method being called is a public static method of the model, and either calls it directly or redirects the call to query builder.


Livewire: Shortening snapshots and hiding FQCN

If models, collections, enums, forms, and objects are stored in public properties in a Livewire component, they end up in a snapshot. By default, Livewire serializes them like this:

{
  "user":  ["mdl",   {"..." : "..."}, {"class": "App\\Models\\User"}],
  "posts": ["elcln", {"..." : "..."}, {"class": "Illuminate\\Database\\Eloquent\\Collection", "modelClass": "App\\Models\\Post"}],
  "form":  ["frm",   {"..." : "..."}, {"class": "App\\Livewire\\Forms\\PostForm"}]
}
Enter fullscreen mode Exit fullscreen mode

Aliaser registers its own synthesizers for:

  • Models,
  • Eloquent‑collections,
  • Collections Illuminate\Support\Collection,
  • Livewire forms,
  • Enums,
  • Ordinary objects (DTOs, value objects, etc.).

After registering aliases, snapshot becomes more compact.:

{
  "user":  ["mdl-alias",   {"..." : "..."}, {"class": "user"}],
  "posts": ["elcln-alias", {"..." : "..."}, {"class": "elqn_clctn", "modelClass": "post"}],
  "form":  ["frm-alias",   {"..." : "..."}, {"class": "postForm"}]
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • user, post, postForm are aliases from registries;
  • elqn_clctn — service alias for the standard Eloquent\Collection;
  • the classes themselves, their namespaces, and the internal architecture of the application are not exposed to the outside.

This has two noticeable effects:

  1. Less HTML/JSON — snapshots become easier, especially if there are many models and collections in the component state.
  2. Fewer leaks of the application structureApp\Domain\*, App\Billing\*, etc. are no longer visible in the frontend.

Polymorphic relationships and morph map

The package can automatically synchronize the model registry with the Eloquent morph map. When the option is enabled:

// config/aliaser.php
return [
    'use_morph_map' => true,
];
Enter fullscreen mode Exit fullscreen mode

calls to modelsMap(["..."]) not only register aliases, but also call Relation::enforceMorphMap() with the same map. As a result, aliases (user, post, etc.) will be stored in the polymorphic relationship tables in the database, rather than the full class names.

This simplifies migrations, refactoring of namespaces, and makes the data in the database more stable when moving models.


Registries and support functions

Each type of entity is stored in its own registry:

  • ModelRegistry
  • FormRegistry
  • ObjectRegistry
  • CollectionRegistry
  • EnumRegistry

They all have a common basic interface.:

ModelRegistry::map([...]);
ModelRegistry::register('user', User::class);
ModelRegistry::find('user');               // FQCN или null
ModelRegistry::aliasForClass(User::class); // 'user'
ModelRegistry::getMap();                   // ['user' => 'App\Models\User', ...]
ModelRegistry::forget('user');
ModelRegistry::clear();
Enter fullscreen mode Exit fullscreen mode

For convenience, there are global wrapper functions:

modelsMap([...]);
formsMap([...]);
objectsMap([...]);
collectionsMap([...]);
enumsMap([...]);
Enter fullscreen mode Exit fullscreen mode

They accept an array of ['alias' => ClassName::class] and the optional $overwrite flag. If you try to register an existing alias without permission to overwrite, informative exceptions will be thrown.


Integration with the console

The package adds several Artisan commands:

php artisan aliaser:install    # installing the configuration and quick guide
php artisan aliaser:list       # list of all registered aliases
php artisan aliaser:help       # overview of package features
php artisan aliaser:help models
php artisan aliaser:help forms
php artisan aliaser:help objects
php artisan aliaser:help collections
php artisan aliaser:help enums
php artisan aliaser:help livewire
Enter fullscreen mode Exit fullscreen mode

The aliaser:list command can output data both in tabular form and in JSON (--json), as well as filter by type (--models, --forms, etc.).


Practical scenarios

Here are some examples where Aliaser has proved useful.

1. Clean controllers and services

Instead of a long list of use directives:

use App\Models\User;
use App\Models\Post;
use App\Models\Comment;
use App\Models\Category;
use App\Models\Tag;

class DashboardController
{
    public function __invoke()
    {
        return [
            'users'      => User::latest()->take(5)->get(),
            'posts'      => Post::published()->take(10)->get(),
            'comments'   => Comment::pending()->count(),
            'categories' => Category::withCount('posts')->get(),
            'tags'       => Tag::popular()->get(),
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

You can do with one facade:

use Entity;

class DashboardController
{
    public function __invoke()
    {
        return [
            'users'      => Entity::user()->latest()->take(5)->get(),
            'posts'      => Entity::post()->published()->take(10)->get(),
            'comments'   => Entity::comment()->pending()->count(),
            'categories' => Entity::category()->withCount('posts')->get(),
            'tags'       => Entity::tag()->popular()->get(),
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Refactoring without migrations

If the comment table uses polymorphic relationships and a post is stored in the morph type, you can safely migrate the model:

// Before
App\Models\Post

// After
App\Domain\Blog\Models\Post
Enter fullscreen mode Exit fullscreen mode

It is enough to update the alias registration:

modelsMap([
    'post' => \App\Domain\Blog\Models\Post::class,
]);
Enter fullscreen mode Exit fullscreen mode

and do not change anything in the database.

3. Livewire ‑ Rich state components

When there are many models, forms, enums, and objects in a component, snapshots grow rapidly. Using aliases reduces the amount of data transferred and hides the application structure from the front-end, without forcing you to manually serialize everything into arrays.


When is Aliaser particularly appropriate?

The package is especially useful in projects where:

  • DDD/ domain splitting is used, and FQCNs become very long;
  • actively used Livewire with rich component states;
  • there are many polymorphic relationships, and I want to store short values in the database;
  • it is important to optimize the size of HTML/JSON and minimize leaks of internal structure to the frontend.

Package Link

Sources and details are on GitHub:

https://github.com/sindyko/aliaser

It is installed via Composer:

composer require sindyko/aliaser
Enter fullscreen mode Exit fullscreen mode

If you have any development ideas — new registry types, additional integrations, or use cases — pull requests and issues are always welcome.

Top comments (0)