adelf / laravel-lift

Take your Eloquent Models to the next level

Home Page:https://wendell-adriel.gitbook.io/laravel-lift/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Lift for Laravel

🏋️ Lift for Laravel

Take your Eloquent Models to the next level

Packagist PHP from Packagist Laravel Version GitHub Workflow Status (main)

Documentation | Installation | Attributes | Methods | Commands | Credits | Contributing

Lift is a package that boosts your Eloquent Models in Laravel.

It lets you create public properties in Eloquent Models that match your table schema. This makes your models easier to read and work with in any IDE.

The package intelligently uses PHP 8’s attributes, and gives you complete freedom in setting up your models. For instance, you can put validation rules right into your models - a simple and easy-to-understand arrangement compared to a separate request class. Plus, all these settings are easily reachable through handy new methods.

With a focus on simplicity, Lift depends on Eloquent Events to work. This means the package fits easily into your project, without needing any major changes (unless you’ve turned off event triggering).

To start using Lift, you just need to add the Lift trait to your Eloquent Models, and you're ready to go.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;
}

Documentation

Docs Button

Installation

composer require wendelladriel/laravel-lift

Attributes

While the Lift trait provides a way to set public properties on your model, the Attributes take your model to the next level.

Cast

The Cast attribute allows you to cast your model's public properties to a specific type and also to type your public properties. It works the same way as it would be using the casts property on your model, but you can set it directly on your public properties.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    public string $name;

    #[Cast('float')]
    public float $price;

    #[Cast('int')]
    public int $category_id;

    #[Cast('boolean')]
    public bool $is_active;

    #[Cast('immutable_datetime')]
    public CarbonImmutable $promotion_expires_at;
}

Fillable

When you use the Lift trait, your model's public properties are automatically set as guarded. You can use the Fillable attribute to set your public properties as fillable.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Fillable]
    public string $name;

    #[Fillable]
    #[Cast('float')]
    public float $price;

    #[Fillable]
    #[Cast('int')]
    public int $category_id;

    #[Fillable]
    #[Cast('boolean')]
    public bool $is_active;

    #[Fillable]
    #[Cast('immutable_datetime')]
    public CarbonImmutable $promotion_expires_at;
}

Hidden

The Hidden attribute allows you to hide your model's public properties the same way as you would do using the hidden property on your model, but you can set it directly on your public properties.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\Hidden;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Fillable]
    public string $name;

    #[Fillable]
    #[Cast('float')]
    public float $price;

    #[Fillable]
    #[Cast('int')]
    public int $category_id;

    #[Fillable]
    #[Cast('boolean')]
    public bool $is_active;

    #[Fillable]
    #[Cast('immutable_datetime')]
    public CarbonImmutable $promotion_expires_at;

    #[Hidden]
    #[Fillable]
    public string $sensitive_data;
}

Validation

Lift provides three attributes to help you validate your model's public properties.

Rules

⚠️ The rules will be validated only when you save your model (create or update)

The Rules attribute allows you to set your model's public properties validation rules the same way as you would do with the rules function on a FormRequest, but you can set it directly on your public properties.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\Hidden;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Rules(['required', 'string'])]
    #[Fillable]
    public string $name;

    #[Rules(['required', 'numeric'])]
    #[Fillable]
    #[Cast('float')]
    public float $price;

    #[Rules(['required', 'integer'])]
    #[Fillable]
    #[Cast('int')]
    public int $category_id;

    #[Rules(['required', 'boolean'])]
    #[Fillable]
    #[Cast('boolean')]
    public bool $is_active;

    #[Rules(['required', 'date_format:Y-m-d H:i:s'])]
    #[Fillable]
    #[Cast('immutable_datetime')]
    public CarbonImmutable $promotion_expires_at;

    #[Rules(['required', 'string'])]
    #[Hidden]
    #[Fillable]
    public string $sensitive_data;
}

You can also pass a second parameter to the Rules attribute to set a custom error message for the validation rule.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    public string $name;
}

CreateRules

⚠️ The rules will be validated only when you create your model

The CreateRules attribute works the same way as the Rules attribute, but the rules will be validated only when you create your model.

In the example below the name property will be validated with the set rules for both when creating and updating the model. The email and password properties will be validated only when creating the model.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\CreateRules;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

class User extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Fillable]
    #[Rules(rules: ['required', 'string'], messages: ['required' => 'The user name cannot be empty'])]
    public string $name;

    #[Fillable]
    #[CreateRules(rules: ['required', 'email'], messages: ['required' => 'The user email cannot be empty'])]
    public string $email;

    #[Fillable]
    #[CreateRules(['required', 'string', 'min:8'])]
    public string $password;
}

UpdateRules

⚠️ The rules will be validated only when you update your model

The UpdateRules attribute works the same way as the Rules attribute, but the rules will be validated only when you update your model.

In the example below the name property will be validated with the set rules for both when creating and updating the model. The email and password properties will be validated only when updating the model.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Attributes\UpdateRules;
use WendellAdriel\Lift\Lift;

class User extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Fillable]
    #[Rules(rules: ['required', 'string'], messages: ['required' => 'The user name cannot be empty'])]
    public string $name;

    #[Fillable]
    #[UpdateRules(rules: ['required', 'email'], messages: ['required' => 'The user email cannot be empty'])]
    public string $email;

    #[Fillable]
    #[UpdateRules(['required', 'string', 'min:8'])]
    public string $password;
}

Mixing Rules

You can also mix the three validation attributes to set different rules for creating and updating your model.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\CreateRules;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Attributes\UpdateRules;
use WendellAdriel\Lift\Lift;

class User extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Fillable]
    #[Rules(rules: ['required', 'string'], messages: ['required' => 'The user name cannot be empty'])]
    public string $name;

    #[Fillable]
    #[CreateRules(rules: ['required', 'email'], messages: ['required' => 'The user email cannot be empty'])]
    #[UpdateRules(['sometimes', 'email'])]
    public string $email;

    #[Fillable]
    #[CreateRules(['required', 'string', 'min:8'])]
    #[UpdateRules(rules: ['sometimes', 'string', 'min:8'], messages: ['min' => 'The password must be at least 8 characters long'])]
    public string $password;
}

Config

The Config attribute allows you to set your model's public properties configurations for the attributes: Cast, Column, Fillable, Hidden, Immutable, Rules and Watch.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Config;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Config(fillable: true, rules: ['required', 'string'], messages: ['required' => 'The PRODUCT NAME field cannot be empty.'])]
    public string $name;

    #[Config(fillable: true, column: 'description', rules: ['required', 'string'])]
    public string $product_description;

    #[Config(fillable: true, cast: 'float', default: 0.0, rules: ['sometimes', 'numeric'], watch: ProductPriceChanged::class)]
    public float $price;

    #[Config(fillable: true, cast: 'int', hidden: true, rules: ['required', 'integer'])]
    public int $random_number;

    #[Config(fillable: true, cast: 'immutable_datetime', immutable: true , rules: ['required', 'date_format:Y-m-d H:i:s'])]
    public CarbonImmutable $expires_at;
}

Primary Key

By default, the Eloquent Model uses the id column as the primary key as an auto-incrementing integer value. With the PrimaryKey attribute you can configure in a simple and easy way the primary key of your model.

If your model uses a different column as the primary key, you can set it using the PrimaryKey attribute:

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $custom_id;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    public string $name;
}

If your model uses a column with a different type and not incrementing like a UUID, you can set it using the PrimaryKey attribute like this:

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[PrimaryKey(type: 'string', incrementing: false)]
    public string $uuid;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    public string $name;
}

DB

The DB class attribute allows you to customize the database connection, table and timestamps of your model. If you don't set any of the attribute parameters, the default values will be used.

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\DB;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

#[DB(connection: 'mysql', table: 'custom_products_table', timestamps: false)]
final class Product extends Model
{
    use Lift;

    #[PrimaryKey(type: 'string', incrementing: false)]
    public string $uuid;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    public string $name;
}

Column

The Column attribute allows you to customize the column name of your model's public properties. In the example below the product_name property will be mapped to the name column on the database table:

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Column;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    #[Column('name')]
    public string $product_name;
}

You can also set a default value for your public properties using the Column attribute. In the example below the price property will be mapped to the price column on the database table and will have a default value of 0.0:

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Column;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    #[Column('name')]
    public string $product_name;
    
    #[Column(default: 0.0)]
    #[Cast('float')]
    public float $price;
}

You can also set a default value for your public properties passing a function name as the default value:

use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Column;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\PrimaryKey;
use WendellAdriel\Lift\Attributes\Rules;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[PrimaryKey]
    public int $id;

    #[Rules(['required', 'string'], ['required' => 'The Product name can not be empty'])]
    #[Fillable]
    #[Column('name')]
    public string $product_name;
    
    #[Column(default: 0.0)]
    #[Cast('float')]
    public float $price;
    
    #[Column(default: 'generatePromotionalPrice')]
    #[Cast('float')]
    public float $promotional_price;
    
    public function generatePromotionalPrice(): float
    {
        return $this->price * 0.8;
    }
}

Immutable

The Immutable attribute allows you to set your model's public properties as immutable. This means that once the model is created, the public properties will not be able to be changed. If you try to change the value of an immutable property an WendellAdriel\Lift\Exceptions\ImmutablePropertyException will be thrown.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\Immutable;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Immutable]
    #[Fillable]
    public string $name;

    #[Fillable]
    #[Cast('float')]
    public float $price;
}

Example:

$product = Product::create([
    'name' => 'Product Name',
    'price' => 10.0,
]);

$product->name = 'New Product Name';
$product->save(); // Will throw an ImmutablePropertyException

Watch

By default, Eloquent already fires events when updating models, but it is a generic event. With the Watch attribute you can set a specific event to be fired when a specific public property is updated. The event will receive as a parameter the updated model instance.

use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use Tests\Datasets\PriceChangedEvent;
use Tests\Datasets\RandomNumberChangedEvent;
use WendellAdriel\Lift\Attributes\Cast;
use WendellAdriel\Lift\Attributes\Fillable;
use WendellAdriel\Lift\Attributes\Watch;
use WendellAdriel\Lift\Lift;

final class Product extends Model
{
    use Lift;

    #[Fillable]
    public string $name;

    #[Watch(PriceChangedEvent::class)]
    #[Fillable]
    #[Cast('float')]
    public float $price;

    #[Fillable]
    #[Cast('int')]
    public int $random_number;

    #[Fillable]
    #[Cast('immutable_datetime')]
    public CarbonImmutable $expires_at;
}
final class PriceChangedEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Product $product,
    ) {
    }
}

Relationships

With Lift, you can configure all of your Model relationships using Attributes. It works the same way when defining them with methods, so all of them accept the same parameters as the methods.

BelongsTo

#[BelongsTo(User::class)]
final class Post extends Model
{
    use Lift;
    // ...
}

BelongsToMany

#[BelongsToMany(Role::class)]
final class User extends Model
{
    use Lift;
    // ...
}
#[BelongsToMany(User::class)]
final class Role extends Model
{
    use Lift;
    // ...
}

HasMany

#[HasMany(Post::class)]
final class User extends Model
{
    use Lift;
    // ...
}

HasManyThrough

#[HasMany(User::class)]
#[HasManyThrough(Post::class, User::class)]
final class Country extends Model
{
    use Lift;
    // ...
}
#[HasMany(Post::class)]
final class User extends Model
{
    use Lift;
    // ...
}
#[BelongsTo(User::class)]
final class Post extends Model
{
    use Lift;
    // ...
}

HasOne

#[HasOne(Phone::class)]
final class User extends Model
{
    use Lift;
    // ...
}

HasOneThrough

#[HasOneThrough(Manufacturer::class, Computer::class)]
#[HasOne(Computer::class)]
final class Seller extends Model
{
    use Lift;
    // ...
}
#[HasOne(Manufacturer::class)]
final class Computer extends Model
{
    use Lift;
    // ...
}

MorphMany/MorphTo

#[MorphMany(Image::class, 'imageable')]
final class Post extends Model
{
    use Lift;
    // ...
}
#[MorphTo('imageable')]
final class Image extends Model
{
    use Lift;
    // ...
}

MorphOne/MorphTo

#[MorphOne(Image::class, 'imageable')]
final class User extends Model
{
    use Lift;
    // ...
}
#[MorphTo('imageable')]
final class Image extends Model
{
    use Lift;
    // ...
}

MorphToMany/MorphedByMany

#[MorphToMany(Tag::class, 'taggable')]
final class Post extends Model
{
    use Lift;
    // ...
}
#[MorphedByMany(Post::class, 'taggable')]
final class Tag extends Model
{
    use Lift;
    // ...
}

Methods

When using the Lift trait, your model will have some new methods available.

customColumns

The customColumns method returns an array with all the public properties that have a custom column name set.

$productCustomColumns = Product::customColumns();

// WILL RETURN
[
    'product_name' => 'name',
]

defaultValues

The defaultValues method returns an array with all the public properties that have a default value set.

If the default value is a function, the function name will be returned instead of the function result since this is a static call.

$productDefaultValues = Product::defaultValues();

// WILL RETURN
[
    'price' => 0.0,
    'promotional_price' => 'generatePromotionalPrice',
]

immutableProperties

The immutableProperties method returns an array with all the public properties that are immutable.

$productImmutableProperties = Product::immutableProperties();

// WILL RETURN
[
    'name',
]

validationRules

The validationRules method returns an array with all the validation rules for your model's public properties.

$productRules = Product::validationRules();

// WILL RETURN
[
    'name' => ['required', 'string'],
    'price' => ['required', 'numeric'],
    'random_number' => ['required', 'integer'],
    'expires_at' => ['required', 'date_format:Y-m-d H:i:s'],
]

createValidationRules

The createValidationRules method returns an array with all the create action validation rules for your model's public properties.

$productRules = Product::createValidationRules();

// WILL RETURN
[
    'name' => ['required', 'string'],
    'price' => ['required', 'numeric'],
]

updateValidationRules

The updateValidationRules method returns an array with all the update action validation rules for your model's public properties.

$productRules = Product::updateValidationRules();

// WILL RETURN
[
    'name' => ['required', 'string'],
    'price' => ['required', 'numeric'],
]

validationMessages

The validationMessages method returns an array with all the validation messages for your model's public properties.

$productRules = Product::validationMessages();

// WILL RETURN
[
    'name' => [
        'required' => 'The PRODUCT NAME field cannot be empty.',
    ],
    'price' => [],
    'random_number' => [],
    'expires_at' => [],
]

createValidationMessages

The createValidationMessages method returns an array with all the validation create action messages for your model's public properties.

$productRules = Product::createValidationMessages();

// WILL RETURN
[
    'name' => [
        'required' => 'The PRODUCT NAME field cannot be empty.',
    ],
    'price' => [],
]

updateValidationMessages

The updateValidationMessages method returns an array with all the validation update action messages for your model's public properties.

$productRules = Product::updateValidationMessages();

// WILL RETURN
[
    'name' => [
        'required' => 'The PRODUCT NAME field cannot be empty.',
    ],
    'price' => [],
]

watchedProperties

The watchedProperties method returns an array with all the public properties that have a custom event set.

$productWatchedProperties = Product::watchedProperties();

// WILL RETURN
[
    'price' => PriceChangedEvent::class,
]

Commands

lift:migration

⚠️ This is an experimental feature, keep that in mind when using it

The lift:migration command allows you to generate a migration file based on your models. By default it uses the App\Models namespace, but you can change it using the --namespace option.

All the created migration files will be placed inside the database/migrations folder.

Examples:

The command below will generate a migration file for the App\Models\User model.

php artisan lift:migration User

The command below will generate a migration file for the App\Models\Auth\User model.

php artisan lift:migration Auth\User

The command below will generate a migration file for the App\Custom\Models\User model.

php artisan lift:migration User --namespace=App\Custom\Models

Create Table Migration

When the table for your model is not yet created in the database, the lift:migration command will generate a migration file to create the table.

// User.php

final class User extends Model
{
    use Lift, SoftDeletes;

    public int $id;

    public string $name;

    public string $email;

    public string $password;

    public CarbonImmutable $created_at;

    public DateTime $updated_at;

    public ?bool $active;

    public $test;
}

// Migration file generated

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('password');
            $table->boolean('active')->nullable();
            $table->string('test');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};

Update Table Migration

When the table for your model is already created in the database, the lift:migration command will generate a migration file to update the table based in the differences between the model and the database table.

// User.php

final class User extends Model
{
    use Lift, SoftDeletes;

    public int $id;

    public string $name;

    public string $username;

    public string $email;

    public string $password;

    public ?bool $active;
}

// Migration file generated

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('username')->after('name');
            $table->dropColumn('created_at');
            $table->dropColumn('updated_at');
            $table->dropColumn('test');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        // Nothing to do here
    }
};

Credits

Contributing

Check the Contributing Guide.

About

Take your Eloquent Models to the next level

https://wendell-adriel.gitbook.io/laravel-lift/

License:MIT License


Languages

Language:PHP 100.0%