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 Computed Attributes Laravel Package

korridor/laravel-computed-attributes

Adds “computed attributes” to Laravel models, letting you define dynamic/derived properties that behave like normal attributes (including access/casting/serialization) without storing them in the database. Useful for clean model APIs and reusable calculations.

View on GitHub
Deep Wiki
Context7
## Getting Started

### **First Steps**
1. **Installation**
   ```bash
   composer require korridor/laravel-computed-attributes
   php artisan vendor:publish --provider="Korridor\ComputedAttributes\ComputedAttributesServiceProvider" --tag="config"
  • Verify config/computed-attributes.php exists (default settings work for most cases).
  • Note: Supports Laravel 10/11/12/13 (check releases for version-specific guidance).
  1. Define a Computed Attribute Add the trait to your Eloquent model and declare computed attributes:

    use Korridor\ComputedAttributes\HasComputedAttributes;
    
    class Order extends Model
    {
        use HasComputedAttributes;
    
        protected $computedAttributes = [
            'total_amount' => function () {
                return $this->items->sum('price');
            },
        ];
    }
    
    • The package automatically persists total_amount to the database when the model is saved.
  2. First Test

    $order = Order::find(1);
    $order->total_amount; // Computes and caches (if enabled)
    $order->save();       // Persists to DB
    

Implementation Patterns

1. Core Workflows

A. Basic Computation

protected $computedAttributes = [
    'formatted_date' => function () {
        return Carbon::parse($this->created_at)->format('Y-m-d');
    },
];

B. Conditional Logic

'status_badge' => function () {
    return $this->is_active ? 'success' : 'danger';
},

C. Dynamic Dependencies

'tax_included_price' => function () {
    return $this->price * (1 + $this->tax_rate / 100);
},

D. External Data Integration

'weather' => function () {
    return Cache::remember("weather_{$this->id}", 3600, fn() =>
        Http::get("https://api.weather.com/{$this->location}")->json()
    );
},

2. Advanced Patterns

A. Caching Strategies

  • Disable Caching (for volatile data):
    'timestamp' => [
        'computation' => fn() => now()->timestamp,
        'cache' => false,
    ],
    
  • Custom TTL:
    'last_updated' => [
        'computation' => fn() => now(),
        'cache_ttl' => 60, // 1 minute
    ],
    

B. Bulk Operations

  • Recompute All Computed Attributes:
    Order::where('updated_at', '<', now()->subDays(1))->each(function ($order) {
        $order->recomputeComputedAttributes();
        $order->save();
    });
    

C. API Responses

  • Auto-Append to JSON:
    protected $appends = ['full_name', 'total_amount'];
    

D. Observers for Side Effects

class OrderObserver
{
    public function saved(Order $order)
    {
        if ($order->isDirty('items')) {
            $order->recomputeComputedAttribute('total_amount');
        }
    }
}

3. Database Integration

A. Schema Migration

  • Add Columns:
    Schema::table('orders', function (Blueprint $table) {
        $table->decimal('total_amount', 10, 2);
    });
    
  • Use Virtual Columns (MySQL 5.7+):
    $table->virtualAs('total_amount', 'SUM(price)')->stored;
    
    • Hybrid Approach: Use the package for PHP-level logic and raw SQL for performance.

B. Indexing

  • Optimize Queries:
    Schema::table('orders', function (Blueprint $table) {
        $table->index('total_amount');
    });
    

4. Testing

A. Unit Tests

public function test_computed_attribute()
{
    $order = new Order(['items' => [['price' => 100]]]);
    $this->assertEquals(100, $order->total_amount);
}

B. Feature Tests

public function test_persistence()
{
    $order = Order::factory()->create(['items' => [['price' => 50]]]);
    $order->refresh();
    $this->assertEquals(50, $order->total_amount);
}

C. Edge Cases

  • Null Handling:
    'discount' => function () {
        return $this->discount_percentage ?? 0;
    },
    
  • Circular Dependencies: Avoid:
    'a' => fn() => $this->b,
    'b' => fn() => $this->a,
    

Gotchas and Tips

1. Common Pitfalls

A. Infinite Loops

  • Problem: Circular dependencies between computed attributes.
  • Fix: Use cache: false or restructure logic.

B. Performance Overhead

  • Problem: Heavy computations during save().
  • Fix:
    • Queue Jobs:
      $model->afterSave(function ($model) {
          ComputeHeavyAttribute::dispatch($model, 'complex_value');
      });
      
    • Disable Caching:
      'volatile' => ['computation' => fn() => heavyLogic(), 'cache' => false],
      

C. Database Schema Mismatches

  • Problem: Computed value exceeds column length.
  • Fix: Adjust column type (e.g., text instead of string) or sanitize:
    'truncated_name' => fn() => Str::limit($this->full_name, 50),
    

D. Serialization Issues

  • Problem: Computed attributes missing in JSON.
  • Fix: Add to $appends:
    protected $appends = ['full_name'];
    

2. Debugging Tips

A. Log Computations

protected $computedAttributes = [
    'debug_value' => [
        'computation' => fn() => Log::debug('Computing debug_value'),
        'cache' => false,
    ],
];

B. Check Cache State

$model->isComputedAttributeCached('full_name'); // true/false

C. Force Recompute

$model->recomputeComputedAttribute('full_name');

D. Validate with Artisan

php artisan computed-attributes:validate User --attribute=full_name

3. Configuration Quirks

A. Global Cache TTL

Set in config/computed-attributes.php:

'default_cache_ttl' => 3600, // 1 hour

B. Skip on Create

'skip_on_create' => [
    'computation' => fn() => 'value',
    'skip_on_create' => true,
],

C. Custom Storage

Override storage (e.g., JSON column):

use Korridor\ComputedAttributes\StoresComputedAttributesInJson;

class User extends Model
{
    use HasComputedAttributes, StoresComputedAttributesInJson;
}

4. Extension Points

A. Custom Computation Types

ComputedAttributes::extend('custom', function ($model, $attribute) {
    return strtoupper($model->{$attribute});
});

Usage:

protected $computedAttributes = [
    'uppercase_name' => ['type' => 'custom'],
];

B. Event Hooks

ComputedAttributes::computing(function ($model, $attribute) {
    Log::info("Computing {$attribute} for {$model->id}");
});

C. Database Drivers

Extend for PostgreSQL/SQLite:

// app/Providers/ComputedAttributesServiceProvider.php
public function register()
{
    if (config('database.default') === 'pgsql') {
        $this->app->bind('computed-attributes.driver', \App\Services\PostgresDriver::class);
    }
}

5. Laravel 13+ Specifics

A. New Features

  • PHP 8.3 Support: Use named arguments and new attributes.
  • Type Safety: Enhanced return type hints in the package.
  • GitHub Actions: PHPStan integration for static analysis.

B. Breaking Changes

  • Laravel 10+ Only: Downgrade to 2.2.* for older versions.
  • Strict Types: Ensure your models use strict_types=1.

C. Performance

  • Optimized Recomputation: Checks $model->isDirty() before saving (since `2.
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope