22h/doctrine-garbage-collection-bundle
Installation Add the bundle via Composer:
composer require 22h/doctrine-garbage-collection-bundle
Enable it in config/bundles.php:
return [
// ...
DoctrineGarbageCollectionBundle::class => ['all' => true],
];
Configuration Publish the default config:
php bin/console doctrine:garbage-collection:install
Edit config/packages/doctrine_garbage_collection.yaml to define:
entities: List of entities to clean (e.g., App\Entity\LogEntry).ttl: Time-to-live in seconds (e.g., 86400 for 1 day).batch_size: Number of records to delete per batch (e.g., 1000).First Run Execute the garbage collection manually:
php bin/console doctrine:garbage-collection:run
Verify logs in var/log/dev.log for deleted records.
# config/packages/doctrine_garbage_collection.yaml
doctrine_garbage_collection:
entities:
App\Entity\LogEntry:
ttl: 2592000 # 30 days
batch_size: 500
condition: "logLevel < 400" # Optional: Custom DQL condition
Run via CLI or schedule a cron job:
php bin/console doctrine:garbage-collection:run App\Entity\LogEntry
Service Provider Setup
Register the bundle in config/app.php (Symfony bundles require manual integration in Laravel):
'providers' => [
// ...
DoctrineGarbageCollectionBundle::class,
],
Command Integration Create a Laravel Artisan command to wrap the Symfony command:
// app/Console/Commands/GarbageCollection.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Application;
class GarbageCollection extends Command
{
protected $signature = 'doctrine:gc {entity?}';
protected $description = 'Run Doctrine garbage collection';
public function handle()
{
$app = new Application();
$app->add(new \DoctrineGarbageCollectionBundle\Command\GarbageCollectionCommand());
$app->setAutoExit(false);
$app->run(new \Symfony\Component\Console\Input\ArrayInput([
'command' => 'doctrine:garbage-collection:run',
'entity' => $this->argument('entity'),
]));
}
}
Scheduling
Add to app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('doctrine:gc')->dailyAt('03:00');
}
Dynamic TTL Override TTL per environment using Laravel’s config:
# config/doctrine_garbage_collection.php
ttl:
default: 86400
production: 604800 # 1 week
Modify the bundle’s config loader to merge with Laravel’s config.
Event-Based Triggers
Hook into Laravel events (e.g., user.deleted) to trigger garbage collection:
// app/Providers/EventServiceProvider.php
public function boot()
{
event(new \App\Events\UserDeleted());
}
Listen in a service:
// app/Services/GarbageCollectionService.php
public function handle(UserDeleted $event)
{
$this->callGarbageCollection('App\Entity\TempFile');
}
Custom Conditions Use DQL in config for complex logic:
condition: "createdAt < :date AND status = 'archived'"
Pass parameters via parameters key:
parameters:
date: "now() - interval '30 days'"
Transaction Management
transactional: false
Foreign Key Constraints
ON DELETE CASCADE in migrations if needed.--dry-run to preview deletions:
php bin/console doctrine:garbage-collection:run --dry-run
Performance Impact
batch_size values may lock tables. Start with 100 and monitor.Symfony vs. Laravel Quirks
SymfonyBridge or mock services:
$container = new \Symfony\Component\DependencyInjection\Container();
$container->set('doctrine', $this->app->make('doctrine'));
var/log/ instead of Laravel’s storage/logs/. Redirect via monolog config.Enable Verbose Output
php bin/console doctrine:gc --verbose
Look for:
Deleting X records from App\Entity\YQuery: DELETE FROM ... WHERE ...Check for Orphaned Records Use Doctrine’s query builder to verify:
$em->createQueryBuilder()
->select('e')
->from('App\Entity\LogEntry', 'e')
->where('e.createdAt < :date')
->setParameter('date', new \DateTime('-30 days'))
->getQuery()
->getResult();
Handle Soft Deletes
If using SoftDeletes, exclude the entity from garbage collection or override the delete() method:
// In your entity
public function delete()
{
$this->setDeletedAt(new \DateTime());
$this->em->persist($this);
$this->em->flush();
}
Custom Collectors
Extend \DoctrineGarbageCollectionBundle\Collector\CollectorInterface:
class CustomCollector implements CollectorInterface
{
public function collect(EntityManagerInterface $em, string $entityClass, array $config)
{
// Custom logic (e.g., call an external API)
return $em->createQueryBuilder()
->delete($entityClass, 'e')
->where('e.customField = :value')
->getQuery();
}
}
Register in config:
collectors:
custom:
class: App\Collector\CustomCollector
entities: [App\Entity\CustomEntity]
Pre/Post Hooks Use Laravel’s events to run logic before/after garbage collection:
// Listen to doctrine.gc.before
event(new \App\Events\GarbageCollectionStarting('App\Entity\LogEntry'));
// Listen to doctrine.gc.after
event(new \App\Events\GarbageCollectionCompleted('App\Entity\LogEntry', 42));
Database-Specific Optimizations Add hints for PostgreSQL/MySQL:
options:
mysql: { hint: "/*+ INDEX_SCAN(table_idx) */" }
pgsql: { hint: "/*+ IndexScan(table_idx) */" }
deleted_at table and index it for Scout.spatie/laravel-housekeeping for a unified approach.How can I help you explore Laravel packages today?