Skip to content

Commit

Permalink
Add command for cart pruning (#1303)
Browse files Browse the repository at this point in the history
This PR adds a command `lunar:prune:carts` and some new config params
under `lunar.cart.prune_tables` to determine how pruning should be
carried out.

I opted for using a pipeline for amending the query, as we ultimately
always seem to end up there.

I've tried to default to some sensible settings (90 days, dont prune
carts with orders associated).

If pruning is enabled we automatically add a daily task rather then
needing the developer to do it.

---------

Co-authored-by: Glenn Jacobs <glenn@neondigital.co.uk>
  • Loading branch information
ryanmitchell and glennjacobs authored Jan 15, 2024
1 parent ad25097 commit a243a91
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 0 deletions.
30 changes: 30 additions & 0 deletions docs/core/reference/carts.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,33 @@ return [
```

In most cases you won't need to change this.

## Pruning cart data

Over time you will experience a build up of carts in your database that you may want to regularly remove.

You can enable automatic removal of these carts using the `lunar.carts.prune_tables.enabled` config. By setting this to `true` any carts without an order associated will be removed after 90 days.

You can change the number of days carts are retained for using the `lunar.carts.prune_tables. prune_interval` config.

If you have specific needs around pruning you can also change the `lunar.carts.prune_tables.pipelines` array to determine what carts should be removed.



```php
return [
// ...
'prune_tables' => [

'enabled' => false,

'pipelines' => [
Lunar\Pipelines\CartPrune\PruneAfter::class,
Lunar\Pipelines\CartPrune\WithoutOrders::class,
],

'prune_interval' => 90, // days

],
];
```
22 changes: 22 additions & 0 deletions packages/core/config/cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,26 @@
'lines.purchasable.product',
'lines.cart.currency',
],

/*
|--------------------------------------------------------------------------
| Prune carts
|--------------------------------------------------------------------------
|
| Should the cart models be pruned to prevent data build up and
| some settings controlling how pruning should be determined
|
*/
'prune_tables' => [

'enabled' => false,

'pipelines' => [
Lunar\Pipelines\CartPrune\PruneAfter::class,
Lunar\Pipelines\CartPrune\WithoutOrders::class,
],

'prune_interval' => 90, // days

],
];
54 changes: 54 additions & 0 deletions packages/core/src/Console/Commands/PruneCarts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Lunar\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Pipeline\Pipeline;
use Lunar\Models\Cart;

class PruneCarts extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lunar:prune:carts';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Prune the carts table';

/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->info('Beginning prune');

$query = Cart::query();

$carts = app(Pipeline::class)
->send($query)
->through(
config('lunar.cart.prune_tables.pipelines', [])
)->then(function ($query) {
$query->chunk(200, function ($carts) {
$carts->each(function ($cart) {
Cart::where('merged_id', $cart->id)->update(['merged_id' => null]);

$cart->lines()->delete();
$cart->addresses()->delete();
$cart->delete();
});
});
});

$this->info('Prune complete');
}
}
9 changes: 9 additions & 0 deletions packages/core/src/LunarServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Cartalyst\Converter\Laravel\Facades\Converter;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Database\Events\MigrationsStarted;
use Illuminate\Database\Events\NoPendingMigrations;
Expand Down Expand Up @@ -39,6 +40,7 @@
use Lunar\Console\Commands\Import\AddressData;
use Lunar\Console\Commands\MigrateGetCandy;
use Lunar\Console\Commands\Orders\SyncNewCustomerOrders;
use Lunar\Console\Commands\PruneCarts;
use Lunar\Console\Commands\ScoutIndexerCommand;
use Lunar\Console\InstallLunar;
use Lunar\Database\State\ConvertProductTypeAttributesToProducts;
Expand Down Expand Up @@ -204,7 +206,14 @@ public function boot(): void
ScoutIndexerCommand::class,
MigrateGetCandy::class,
SyncNewCustomerOrders::class,
PruneCarts::class,
]);

if (config('lunar.cart.prune_tables.enabled', false)) {
$this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
$schedule->command('lunar:prune:carts')->daily();
});
}
}

Arr::macro('permutate', [\Lunar\Utils\Arr::class, 'permutate']);
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/Pipelines/CartPrune/PruneAfter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Lunar\Pipelines\CartPrune;

use Closure;
use Illuminate\Database\Eloquent\Builder;

final class PruneAfter
{
public function handle(Builder $query, Closure $next)
{
$days = config('lunar.cart.prune_tables.prune_after', 90);

$query->where('updated_at', '<=', now()->subDays($days))
->whereDoesntHave('lines', function ($query) use ($days) {
$query->where('updated_at', '>', now()->subDays($days));
});

return $next($query);
}
}
16 changes: 16 additions & 0 deletions packages/core/src/Pipelines/CartPrune/WithoutOrders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Lunar\Pipelines\CartPrune;

use Closure;
use Illuminate\Database\Eloquent\Builder;

final class WithoutOrders
{
public function handle(Builder $query, Closure $next)
{
$query->whereDoesntHave('orders');

return $next($query);
}
}
45 changes: 45 additions & 0 deletions packages/core/tests/Unit/Console/CartPruneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Lunar\Tests\Unit\Console;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Lunar\Models\Cart;
use Lunar\Models\Channel;
use Lunar\Models\Currency;
use Lunar\Tests\TestCase;

/**
* @group commands
*/
class CartPruneTest extends TestCase
{
use RefreshDatabase;

/** @test */
public function can_prune_carts_with_default_settings()
{
$currency = Currency::factory()->create();
$channel = Channel::factory()->create();

$cart = Cart::create([
'currency_id' => $currency->id,
'channel_id' => $channel->id,
'meta' => ['foo' => 'bar'],
'updated_at' => Carbon::now()->subDay(120),
]);

$cart = Cart::create([
'currency_id' => $currency->id,
'channel_id' => $channel->id,
'meta' => ['foo' => 'bar'],
'updated_at' => Carbon::now()->subDay(20),
]);

$this->assertCount(2, Cart::query()->get());

$this->artisan('lunar:prune:carts');

$this->assertCount(1, Cart::query()->get());
}
}

0 comments on commit a243a91

Please sign in to comment.