Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Lift Laravel Package

wendelladriel/laravel-lift

Experimental Laravel package that supercharges Eloquent models with typed public properties matching your schema, powered by PHP 8 attributes. Add validation rules and other metadata directly on models and access them via handy methods, using Eloquent events for easy drop-in use.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require wendelladriel/laravel-lift
    

    Add the Lift trait to your Eloquent model:

    use WendellAdriel\Lift\Lift;
    
    class Product extends Model
    {
        use Lift;
        // ...
    }
    
  2. First Use Case: Define a typed public property with attributes (e.g., Fillable, Cast, Rules):

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

    This automatically configures the model to:

    • Only allow mass assignment via $fillable (via Fillable attribute).
    • Apply validation rules (via Rules attribute).
    • Type-hint the property as string.
  3. First Interaction: Use castAndCreate to create a model with type-safe casting:

    $product = Product::castAndCreate(['name' => 'Laptop']);
    

Implementation Patterns

1. Attribute-Driven Model Configuration

Replace traditional $fillable, $casts, and $rules with attributes for cleaner, self-documenting code:

final class Product extends Model
{
    use Lift;

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

    #[Fillable]
    #[Cast('float')]
    #[Rules(['nullable', 'min:0'])]
    public ?float $price = null;
}

Benefits:

  • No need to maintain separate arrays ($fillable, $casts, etc.).
  • IDE autocompletion for attributes (e.g., #[Cast('...')]).
  • Type safety for public properties.

2. Type-Safe CRUD Operations

Use Lift’s casting methods to ensure type safety during model operations:

// Create
$product = Product::castAndCreate(['name' => 'Phone', 'price' => '99.99']);

// Update
$product->castAndUpdate(['price' => '89.99']);

// Fill (e.g., in a form request)
$product->castAndFill($request->validated());

Why?

  • Automatically casts input values to the correct type (e.g., string to float).
  • Avoids runtime errors from mismatched types.

3. Immutable Properties

Protect sensitive fields (e.g., created_by) from modification:

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

Use Case:

  • Audit logs, user-generated content, or read-only fields.
  • Throws ImmutablePropertyException if modified after creation.

4. Event-Driven Workflows

Trigger custom events when specific properties change:

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

Example Event:

class PriceUpdatedEvent
{
    use Dispatchable;

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

When to Use:

  • Notifications (e.g., "Price changed" email).
  • Analytics (e.g., track price adjustments).
  • Side effects (e.g., update related inventory).

5. Relationships as Attributes

Define relationships declaratively:

use WendellAdriel\Lift\Attributes\Relations\BelongsTo;

#[BelongsTo(User::class, 'user_id')]
public ?User $user = null;

Advantages:

  • No need for belongsTo() method definitions.
  • IDE support for relationship navigation (e.g., $product->user).

6. Database Customization

Override default table/connection settings:

#[DB(connection: 'pgsql', table: 'products_v2')]
final class Product extends Model
{
    use Lift;
    // ...
}

Use Case:

  • Multi-database setups (e.g., MySQL for main data, PostgreSQL for analytics).
  • Schema migrations without changing the model class name.

7. Default Values and Computed Fields

Set defaults or computed values via Column attribute:

#[Column(default: 'generateDefaultName')]
public string $slug;

public function generateDefaultName(): string
{
    return Str::slug($this->name);
}

When to Use:

  • Derived fields (e.g., slug from name).
  • Dynamic defaults (e.g., created_at fallback).

8. Integration with Laravel Features

  • Form Requests: Use castAndFill in handle() to validate and cast input:
    public function handle()
    {
        $product = new Product();
        $product->castAndFill($this->validated());
        $product->save();
    }
    
  • API Resources: Leverage typed properties for consistent JSON responses:
    public function toArray($request)
    {
        return [
            'name' => $this->name,
            'price' => $this->price, // Always returns float
        ];
    }
    

Gotchas and Tips

Pitfalls

  1. Attribute Order Matters:

    • Place #[Fillable] before #[Rules] or #[Cast] to avoid conflicts.
    • Example:
      #[Fillable]       // ✅ Correct
      #[Rules(['required'])]
      public string $name;
      
      // ❌ Wrong (may not apply rules)
      #[Rules(['required'])]
      #[Fillable]
      public string $name;
      
  2. Circular Dependencies in Relationships:

    • Avoid bidirectional relationships (e.g., #[BelongsTo] + #[HasMany]) that reference each other without proper foreign keys.
    • Fix: Use explicit foreign key attributes:
      #[BelongsTo(User::class, 'user_id')]
      public ?User $user = null;
      
  3. Immutable Properties and Mass Assignment:

    • Immutable fields cannot be mass-assigned, even with #[Fillable].
    • Workaround: Use castAndSet() for immutable fields:
      $product->castAndSet('created_by', auth()->id());
      
  4. Custom Cast Types:

    • Lift supports standard Eloquent casts ('int', 'array', etc.), but custom casts (e.g., Json or Encrypted) may not work out of the box.
    • Fix: Extend the package or use #[Cast('json')] with manual handling.
  5. Database Column Mismatches:

    • If a #[Column('custom_name')] doesn’t match the DB column, queries will fail.
    • Debug Tip: Use dd($model->getConnection()->getSchemaBuilder()->getColumnListing($model->getTable())) to verify column names.
  6. Event Dispatching Timing:

    • #[Watch] events fire after the model is saved, not during validation.
    • Use Case: Avoid dispatching events in validation logic.

Debugging Tips

  1. Inspect Model Configuration: Use dd($model->getLiftConfiguration()) to debug applied attributes.

  2. Validate Attribute Parsing: Check if attributes are parsed correctly:

    $reflection = new ReflectionClass($model);
    $property = $reflection->getProperty('name');
    $attributes = $property->getAttributes();
    
  3. Override Casting Logic: Extend the Lift trait to customize casting behavior:

    trait CustomLift extends Lift
    {
        protected function customizeCast($key, $value)
        {
            if ($key === 'price' && is_string($value)) {
                return (float) str_replace('$', '', $value);
            }
            return parent::customizeCast($key, $value);
        }
    }
    
  4. Handle Immutable Exceptions: Catch ImmutablePropertyException in controllers:

    try {
        $product->name = 'New Name';
        $product->save();
    } catch (ImmutablePropertyException $e) {
        abort(403, 'Cannot modify immutable field.');
    }
    

Performance Considerations

  1. Attribute Reflection Overhead:

    • Lift uses reflection to parse attributes, which adds minimal overhead (~1-2ms per model initialization).
    • Optimization: Cache reflection results if using high-frequency models.
  2. Event Dispatching:

    • #[Watch] events are dispatched after saving, which may impact performance for bulk operations.
    • Tip: Disable events during batch updates:
      Product::withoutEvents(function () {
          Product::castAndUpdate([...]);
      });
      
  3. Type Casting:

    • Casting complex types (e.g., CarbonImmutable) adds CPU overhead.
    • Tip: Cache cast instances or use simpler types where possible.

Extension Points

  1. Custom Attributes: Extend Lift by creating new attributes (e.g., #[Encrypt]):
    namespace Wendell
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony