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;
}
composer require wendelladriel/laravel-lift
While the Lift
trait provides a way to set public properties on your model, the Attributes
take your model to the next level.
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;
}
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;
}
Lift provides three attributes to help you validate your model's public properties.
⚠️ 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;
}
⚠️ 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;
}
⚠️ 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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
}
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
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,
) {
}
}
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(User::class)]
final class Post extends Model
{
use Lift;
// ...
}
#[BelongsToMany(Role::class)]
final class User extends Model
{
use Lift;
// ...
}
#[BelongsToMany(User::class)]
final class Role extends Model
{
use Lift;
// ...
}
#[HasMany(Post::class)]
final class User extends Model
{
use Lift;
// ...
}
#[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(Phone::class)]
final class User extends Model
{
use Lift;
// ...
}
#[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(Image::class, 'imageable')]
final class Post extends Model
{
use Lift;
// ...
}
#[MorphTo('imageable')]
final class Image extends Model
{
use Lift;
// ...
}
#[MorphOne(Image::class, 'imageable')]
final class User extends Model
{
use Lift;
// ...
}
#[MorphTo('imageable')]
final class Image extends Model
{
use Lift;
// ...
}
#[MorphToMany(Tag::class, 'taggable')]
final class Post extends Model
{
use Lift;
// ...
}
#[MorphedByMany(Post::class, 'taggable')]
final class Tag extends Model
{
use Lift;
// ...
}
When using the Lift
trait, your model will have some new methods available.
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',
]
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',
]
The immutableProperties
method returns an array with all the public properties that are immutable.
$productImmutableProperties = Product::immutableProperties();
// WILL RETURN
[
'name',
]
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'],
]
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'],
]
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'],
]
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' => [],
]
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' => [],
]
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' => [],
]
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,
]
⚠️ 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
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');
}
};
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
}
};
Check the Contributing Guide.