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

Expiry Laravel Package

moox/expiry

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to Begin

  1. Installation:

    composer require moox/expiry
    php artisan mooxexpiry:install
    
    • This publishes migrations, config, and sets up the package.
  2. Define Expirable Models: Add HasExpiry trait to your Eloquent models:

    use Moox\Expiry\Traits\HasExpiry;
    
    class User extends Model
    {
        use HasExpiry;
    }
    
  3. Set Expiry Fields: Configure expiry fields in your model:

    protected $expiryFields = [
        'expires_at' => '2025-12-31', // Carbon instance or string
    ];
    
  4. Run Expiry Checks: Use the Expiry facade to check or clean expired records:

    use Moox\Expiry\Facades\Expiry;
    
    // Check if a model is expired
    $isExpired = Expiry::isExpired($user);
    
    // Clean expired records (e.g., in a cron job)
    Expiry::cleanExpired(User::class);
    
  5. Verify Migrations: Check the published migration (database/migrations/[timestamp]_create_expiry_logs_table.php) for the expiry_logs table, which tracks expired records.


First Use Case: Soft-Deleting Expired Users

  1. Extend HasExpiry to trigger soft deletes:

    use Moox\Expiry\Traits\HasExpiry;
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    class User extends Model
    {
        use HasExpiry, SoftDeletes;
    
        protected $expiryFields = ['expires_at' => now()->addDays(30)];
    
        protected static function bootHasExpiry()
        {
            static::saved(function ($model) {
                if ($model->isExpired()) {
                    $model->delete();
                }
            });
        }
    }
    
  2. Schedule Cleanup: Add a cron job in app/Console/Kernel.php:

    protected function schedule(Schedule $schedule)
    {
        $schedule->command('expiry:clean')->daily();
    }
    

Implementation Patterns

Core Workflows

  1. Model Integration:

    • Trait Usage: Attach HasExpiry to models needing expiry tracking.
    • Field Configuration: Define expiry fields in $expiryFields (supports Carbon instances, timestamps, or static dates).
    • Custom Logic: Override isExpired() or handleExpiry() for bespoke behavior.
  2. Expiry Management:

    • Facade Methods:
      // Check expiry status
      Expiry::isExpired($model);
      
      // Clean expired records (with optional query builder)
      Expiry::cleanExpired(User::class, function ($query) {
          return $query->where('role', 'trial');
      });
      
      // Log expiry events
      Expiry::logExpiry($model, 'manual_cleanup');
      
    • Query Scopes:
      // Filter expired models
      $expiredUsers = User::expired()->get();
      
  3. Logging and Auditing:

    • The expiry_logs table stores:
      • model_type: Fully qualified class name.
      • model_id: Primary key of the expired record.
      • expired_at: Timestamp of expiry.
      • reason: Optional custom reason (e.g., 'subscription_ended').
    • Query logs:
      $logs = ExpiryLog::where('model_type', User::class)->get();
      
  4. Bulk Operations:

    • Use Expiry::cleanExpired() with query constraints to target specific subsets:
      Expiry::cleanExpired(Product::class, function ($query) {
          return $query->where('stock', '<', 10);
      });
      

Integration Tips

  1. Event-Driven Expiry: Listen for expiry events to trigger actions (e.g., send notifications):

    // In EventServiceProvider
    public function boot()
    {
        Expiry::onExpiry(function ($model) {
            Notification::send($model, new ExpiryNotification());
        });
    }
    
  2. API Endpoints: Expose expiry status in API responses:

    Route::get('/users/{user}/expiry', function (User $user) {
        return response()->json([
            'is_expired' => Expiry::isExpired($user),
            'expires_at' => $user->expires_at,
        ]);
    });
    
  3. Admin Panels: Display expiry logs in admin interfaces (e.g., Laravel Nova):

    // Nova Resource
    public static $expiryLogs = [
        'attribute' => 'expiry_logs',
        'label' => 'Expiry History',
    ];
    
  4. Testing: Mock expiry checks in tests:

    $user = User::factory()->create(['expires_at' => now()->subDay()]);
    $this->assertTrue(Expiry::isExpired($user));
    

Gotchas and Tips

Pitfalls

  1. Migration Conflicts:

    • If you manually run migrations after mooxexpiry:install, ensure the expiry_logs table exists. The installer may skip migrations if they’re already up-to-date.
    • Fix: Run php artisan migrate:fresh if migrations fail.
  2. Time Zone Issues:

    • Expiry checks use the system’s default time zone. Explicitly set time zones in config/app.php or models:
      protected $dateFormat = 'Y-m-d H:i:s';
      protected $connection->getDoctrineSchemaManager()->getDatabasePlatform()->getDateTimeType()->getName();
      
  3. Circular Dependencies:

    • Avoid calling isExpired() or cleanExpired() recursively (e.g., in model observers or boot methods). This can cause infinite loops.
    • Fix: Use a flag or guard clause:
      protected static $isCheckingExpiry = false;
      
      public function isExpired()
      {
          if (self::$isCheckingExpiry) return false;
          self::$isCheckingExpiry = true;
          try {
              return parent::isExpired();
          } finally {
              self::$isCheckingExpiry = false;
          }
      }
      
  4. Performance with Large Datasets:

    • cleanExpired() can be slow for tables with millions of records. Use chunking:
      Expiry::cleanExpired(User::class)->chunk(1000);
      
  5. Soft Deletes vs. Expiry:

    • If using SoftDeletes, ensure deleted_at is not null when checking expiry. Override isExpired() to skip soft-deleted models:
      public function isExpired()
      {
          return !$this->deleted_at && parent::isExpired();
      }
      

Debugging

  1. Log Expiry Events: Enable logging in config/expiry.php:

    'log_expiry_events' => env('EXPIRY_LOG_EVENTS', true),
    

    Check storage/logs/laravel.log for expiry-related entries.

  2. Verify Expiry Fields: Ensure $expiryFields contains valid Carbon-compatible values. Use:

    $this->assertInstanceOf(Carbon::class, $model->expires_at);
    
  3. Check for Stale Logs: The expiry_logs table may grow large. Add a cleanup job:

    // In a scheduled command
    ExpiryLog::where('created_at', '<', now()->subYears(1))->delete();
    

Extension Points

  1. Custom Expiry Logic: Override the handleExpiry() method to add logic (e.g., send emails, archive data):

    protected function handleExpiry()
    {
        $this->archive();
        Mail::to($this->email)->send(new ExpiryNotice());
    }
    
  2. Dynamic Expiry Fields: Use accessors to compute expiry dynamically:

    public function getExpiresAtAttribute()
    {
        return now()->addDays($this->trial_days);
    }
    
  3. Expiry Conditions: Add custom conditions to isExpired():

    public function isExpired()
    {
        return parent::isExpired() && $this->status === 'active';
    }
    
  4. Expiry Notifications: Extend the Expiry facade to add custom notification logic:

    // In a service provider
    Expiry::extend(function ($app) {
        $app->bind('expiry.notifier', function () {
            return new SlackNotifier();
        });
    });
    
  5. Bulk Expiry Actions: Create a custom command for bulk expiry operations:

    php artisan expiry:bulk-clean --model=User --reason="end_of_trial"
    

    Implement the command in app/Console/Commands/ExpiryBulkCleanCommand.php.

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.
cocosmos/filament-sticky-save-bar
patrickbussmann/oauth2-apple
3brs/enterprise-security-bundle
anousss007/vigilance
supportpal/eloquent-model
ardenexal/fhir-models
laravel-at/laravel-image-sanitize
romalytar/yammi-audit-log-laravel
ardenexal/fhir-validation
arshaviras/weather-widget
laravel-chronicle/core
sunchayn/nimbus
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope