Inspired by the SoftDeletes trait, this package provides a trait to make Eloquent models expirable.
It relies on an additional attribute (named expires_at
by default) that contains the date of expiration
(or null
to make the model eternal).
When the expiration date is reached, the model will automatically disappear from all the Eloquent query results (but still remain in the database).
- Compatibility
- Installation
- Usage
- License
Laravel Expirable package version | Supported Laravel framework versions |
---|---|
v2 | 10 and 11 |
v1 | 5.8 to 9 |
You're reading the documentation for the latest version (v2) of this package.
Install the package via composer using this command:
composer require alajusticia/laravel-expirable
You can publish the configuration file with:
php artisan vendor:publish --provider="ALajusticia\Expirable\ExpirableServiceProvider"
To make a model expirable, add the Expirable trait provided by this package:
use ALajusticia\Expirable\Traits\Expirable;
use Illuminate\Database\Eloquent\Model;
class Subscription extends Model
{
use Expirable;
This trait will automatically add the expiration attribute in the list of attributes that should be mutated to dates.
By default the package adds an attribute named expires_at
on your model.
You can change this name by setting the EXPIRES_AT
constant (don't forget to set the same name for the column in
the migration, see below).
For example, let's say that we have a Subscription
model and we want the attribute to be ends_at
:
use ALajusticia\Expirable\Traits\Expirable;
use Illuminate\Database\Eloquent\Model;
class Subscription extends Model
{
use Expirable;
const EXPIRES_AT = 'ends_at';
You can also change the attribute name globally for all your expirable models by using the attribute_name
option in
the expirable.php
configuration file (the constant prevails).
If you do change the name globally in the configuration file, you don't have to set the name in the migration as it will be populated automatically.
You can set a default period of validity with the defaultExpiresAt
public static function.
This method must return a date object or null
. This way, on saving the model the date of expiration will be automatically added unless you
explicitly provide a date.
An example to set a default period of validity of six months:
use ALajusticia\Expirable\Traits\Expirable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Subscription extends Model
{
use Expirable;
// ...
public static function defaultExpiresAt()
{
return Carbon::now()->addMonths(6);
}
// Create a new subscription which will expire in six months (using default expiration date)
$subscription = new Subscription;
$subscription->save();
// Create a new subscription which will expire in one year (overwrite the default expiration date)
$subscription = new Subscription;
$subscription->expiresAt(Carbon::now()->addYear());
$subscription->save();
The package requires that you add the expirable column in your migration.
For convenience, the package provides the expirable()
and dropExpirable()
blueprint macros ready to use in your migration files:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->expirable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->dropExpirable();
});
}
};
By default the name of the database column, like the model attribute, will be expires_at
or the one in the configuration file.
If you modified the default name of the attribute on your model with the EXPIRES_AT
constant, you need to set the same custom name for the column
in your migration, by giving the macro a parameter with the name.
To continue with our subscription example:
public function up()
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->expirable('ends_at');
});
}
public function down()
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->dropExpirable('ends_at');
});
}
Do your stuff as usual. By default, when the date of expiration is reached, the model will automatically be excluded of the results.
For example:
// Get only valid and current subscriptions and exclude expired ones
$subscriptions = Subscription::all();
This, and all the next examples, work as well with relationships:
$user = App\User::find(1);
$subscriptions = $user->subscriptions();
To disable the default filtering and retrieve all the models, ignoring their expiration date:
// Get all subscriptions
$subscriptions = Subscription::withExpired()->get();
Use the onlyExpired scope:
// Get expired subscriptions
$subscriptions = Subscription::onlyExpired()->get();
To get only the models which do not expire (with expiration date attribute to null
), use the onlyEternal scope:
// Get unlimited subscriptions
$subscriptions = Subscription::onlyEternal()->get();
This package provides a query scope to retrieve only models that have expired for at least a given period of time.
Use the expiredSince
scope and give it a parameter representing the desired period of time.
The parameter must be a string and the syntax is the same as the syntax accepted by the Carbon sub
method
(see documentation here: https://carbon.nesbot.com/docs/#api-addsub).
For example, let's say that you want to definitively delete from the database the models expired since at least one year, the query will be:
// Delete expired models since one year or more
Subscription::expiredSince('1 year')->delete();
To get the expiration date without having to know the name of its attribute, use the getExpirationDate method:
$subscription->getExpirationDate(); // Returns a date object
If you know the name of the expiration date attribute, you can simply populate this attribute
with a date object (or null
for eternal):
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->ends_at = Carbon::now()->addMonth();
$subscription->save();
Of course it also works with mass assignment, but don't forget to add the attribute you intend to mass assign
(here ends_at
) in the $fillable
property of your model:
// Create a new subscription valid for one month
$subscription = Subscription::create([
'plan' => 'premium',
'ends_at' => Carbon::now()->addMonth(),
]);
Use the expiresAt
method with a date object in parameter (or null
for eternal) to set an expiration date manually.
On an Eloquent query the changes will be directly saved in the database. On a single model you still need to use the
save
method:
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->expiresAt(Carbon::now()->addMonth());
$subscription->save();
// Set multiple subscriptions valid for one year
Subscription::whereKey([1, 2, 3])->expiresAt(Carbon::now()->addYear());
The lifetime
method provides you a more human readable way to set the period of validity with a string.
The parameter must be a string and the syntax is the same as the syntax accepted by the Carbon add
method
(see documentation here: https://carbon.nesbot.com/docs/#api-addsub).
// Create a new subscription valid for one month
$subscription = new Subscription();
$subscription->lifetime('1 month');
$subscription->save();
If you want a model to expire, use the expire
method on a model instance.
This will set the expiration date at the current timestamp.
// Make a model expire
$subscription->expire();
If you know the primary key of the model, you may make it expire without retrieving it by calling the expireByKey
method.
In addition to a single primary key as its argument, the expireByKey
method will accept multiple primary keys,
an array of primary keys, or a collection of primary keys:
Subscription::expireByKey(1);
Subscription::expireByKey(1, 2, 3);
Subscription::expireByKey([1, 2, 3]);
Subscription::expireByKey(collect([1, 2, 3]));
You can also run an expire statement on a set of models:
Subscription::where('plan', 'basic')->expire();
After a model has expired, you can make it valid again using revive()
method.
It accepts an optional parameter which can be a date object or null
for the new period of validity.
Without parameter it resets to the default expiration date or set the expiration attribute to null
if no default
expiration date is set (making the model eternal).
// Reset validity with the default expiration date or set validity for unlimited period
$subscription->revive();
// Set the model to expire in one month
$subscription->revive(Carbon::now()->addMonth());
Sure, it also works with queries:
// Revive by query
Subscription::where('plan', 'plus')->revive();
If you want a model never to expire, you just have to set the expiration attribute to null
.
You can do that manually or for existing models you can use the provided shortcut method makeEternal()
:
// Make a model eternal
$subscription->makeEternal();
// Make eternal by query
Subscription::where('plan', 'business')->makeEternal();
With the extendLifetimeBy
, you can extend the model lifetime by a human readable period (using the same syntax as the lifetime
method):
$subscription->extendLifetimeBy('1 month')->save();
In the same way, you have the ability to shorten the model lifetime with the shortenLifetimeBy
method:
$subscription->shortenLifetimeBy('3 days')->save();
You can reset the expiration date to its default value (null
or the date returned by the defaultExpiresAt
static function):
$subscription->resetExpiration()->save();
You can call the isExpired()
and isEternal()
methods on an expirable model instance. For example:
if ($subscription->isExpired()) {
$user->notify(new RenewalProposal($subscription));
}
This package comes with a command to delete expired records from the database:
php artisan expirable:purge
You have two ways to indicate which models should be purged:
- add their class to the
purge
array of the configuration file:
'purge' => [
\App\Models\Subscription::class,
],
- pass one or several classes in argument of the purge command:
php artisan expirable:purge "App\Models\Subscription" "App\Models\Coupon"
Models passed as arguments take precedence (the purge
array in the configuration file will be ignored).
You can also specify a period of time to delete models expired since that given period, using the since
option (the value of this option is passed to the expiredSince query scope):
php artisan expirable:purge "App\Models\Subscription" --since="2 months"
By default, this command will use force deletion to purge the models. If needed, you can specify the mode
option with the value soft
to perform a soft delete:
php artisan expirable:purge --mode=soft
hard
mode, you will have to specify the desired action for the "on delete" property of the constraint (for example using the onDelete('cascade'), cascadeOnDelete() or nullOnDelete() modifiers on the foreign key in your migrations: https://laravel.com/docs/10.x/migrations#foreign-key-constraints) or purge the child models first (ordering the command arguments or the array in the config file to start with the children) to avoid SQL errors.
Open source, licensed under the MIT license.