PHPackages                             ameax/laravel-change-detection - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ameax/laravel-change-detection

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ameax/laravel-change-detection
==============================

This is my package laravel-change-detection

1244↓33.3%[4 PRs](https://github.com/ameax/laravel-change-detection/pulls)PHPCI passing

Since Sep 22Pushed 1mo agoCompare

[ Source](https://github.com/ameax/laravel-change-detection)[ Packagist](https://packagist.org/packages/ameax/laravel-change-detection)[ RSS](/packages/ameax-laravel-change-detection/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (7)Used By (0)

Laravel Change Detection
========================

[](#laravel-change-detection)

[![Latest Version on Packagist](https://camo.githubusercontent.com/e4c74413518aff53f96797c45f53119b9923b0ac0373c8508ef61d6464699614/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616d6561782f6c61726176656c2d6368616e67652d646574656374696f6e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ameax/laravel-change-detection)[![GitHub Tests Action Status](https://camo.githubusercontent.com/dc1d2ab000a94bd00616ae63d5edd1bc7b902bb5eed793784be4445cd489bc0f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616d6561782f6c61726176656c2d6368616e67652d646574656374696f6e2f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/ameax/laravel-change-detection/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/a2f07334b81939292094cfb41c7821dd2d81aa7e2965b93a74d9cb0d89faf1dd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f616d6561782f6c61726176656c2d6368616e67652d646574656374696f6e2f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/ameax/laravel-change-detection/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/7b320722bba4caddd14000a759fa23b77d1a92b08cde4554532d6e4b1e4f0378/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f616d6561782f6c61726176656c2d6368616e67652d646574656374696f6e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ameax/laravel-change-detection)

A high-performance Laravel package for tracking and detecting changes in Eloquent models using cryptographic hashes. Perfect for audit trails, cache invalidation, external system synchronization, and data integrity monitoring in large-scale applications.

Laravel Change Detection automatically calculates and stores MD5/SHA256 hashes of your model attributes, supports composite dependency tracking, provides MySQL-optimized bulk operations for 100k+ records, and includes comprehensive CLI tools for monitoring and maintenance.

Features
--------

[](#features)

- **🚀 High Performance**: MySQL-optimized hash calculations for 100k+ records with 1000-record batch processing
- **🔄 Composite Dependencies**: Track changes across related models automatically
- **⚡ Bulk Operations**: Efficient batch processing with configurable limits
- **🗃️ Cross-Database Support**: Hash tables can be in different databases than models
- **🛠️ CLI Tools**: Comprehensive commands for monitoring and maintenance
- **📊 Queue Integration**: Background processing with retry logic
- **🔍 Change Detection**: Instant detection of model modifications
- **💾 Soft Delete Support**: Proper handling of deleted records
- **🧹 Cleanup Tools**: Automatic orphaned hash detection and removal
- **📝 Debug Logging**: Built-in LogPublisher for development
- **🎯 Smart Model Discovery**: Automatically discovers models via Publishers and Morph Map
- **🔧 Dependency Tracking**: Efficient dependency building with `has_dependencies_built` flag

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require ameax/laravel-change-detection
```

Publish and run the migrations:

```
php artisan vendor:publish --tag="laravel-change-detection-migrations"
php artisan migrate
```

Optionally, publish the config file:

```
php artisan vendor:publish --tag="laravel-change-detection-config"
```

Configuration
-------------

[](#configuration)

The published config file (`config/change-detection.php`) includes:

```
return [
    // Database connection for hash tables (null = default)
    'database_connection' => null,

    // Custom table names
    'tables' => [
        'hashes' => 'hashes',
        'hash_dependents' => 'hash_dependents',
        'publishers' => 'publishers',
        'publishes' => 'publishes',
    ],

    // Queue configuration
    'queues' => [
        'publish' => 'default',
        'detect_changes' => 'default',
    ],

    // Hash algorithm: 'md5' or 'sha256'
    'hash_algorithm' => 'md5',

    // Retry intervals for failed publishes (in seconds)
    'retry_intervals' => [
        1 => 30,    // First retry after 30 seconds
        2 => 300,   // Second retry after 5 minutes
        3 => 21600, // Third retry after 6 hours
    ],
];
```

Quick Start
-----------

[](#quick-start)

### 1. Make Your Model Hashable

[](#1-make-your-model-hashable)

```
use Ameax\LaravelChangeDetection\Contracts\Hashable;
use Ameax\LaravelChangeDetection\Traits\InteractsWithHashes;
use Illuminate\Database\Eloquent\Model;

class User extends Model implements Hashable
{
    use InteractsWithHashes;

    protected $fillable = ['name', 'email', 'status'];

    // Define which attributes to include in hash calculation
    public function getHashableAttributes(): array
    {
        return ['name', 'email', 'status'];
    }

    // Define child dependencies (optional - for composite hashing)
    public function getHashCompositeDependencies(): array
    {
        return ['posts']; // Hash changes when user's posts change
    }

    // Define parent relations to notify (optional)
    public function getHashParentRelations(): array
    {
        return []; // No parent relations for User
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
```

### 2. Automatic Hash Generation

[](#2-automatic-hash-generation)

Hashes are automatically calculated and stored when models are saved:

```
$user = User::create([
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'status' => 'active'
]);

// Hash is automatically generated and stored
$currentHash = $user->getCurrentHash();
echo $currentHash->composite_hash; // e.g., "a1b2c3d4e5f6..."
```

### 3. Change Detection

[](#3-change-detection)

```
// Check if model has changed since last hash calculation
if ($user->hasHashChanged()) {
    echo "User has been modified!";
}

// Manually recalculate hash
$user->updateHash();

// Force hash update regardless of changes
$newHash = $user->forceHashUpdate();
```

Advanced Usage
--------------

[](#advanced-usage)

### The Hashable Interface

[](#the-hashable-interface)

The `Hashable` interface requires implementing these methods:

```
interface Hashable
{
    // Required: Define which attributes to include in the hash
    public function getHashableAttributes(): array;

    // Optional: Define child relations whose changes affect this model's hash
    public function getHashCompositeDependencies(): array;

    // Optional: Define parent relations to notify when this model changes
    public function getHashParentRelations(): array;

    // Optional: Define a scope to filter which records get hashed
    public function getHashableScope(): ?\Closure;
}
```

The `InteractsWithHashes` trait provides default implementations for optional methods.

### Composite Dependencies

[](#composite-dependencies)

Track changes across related models:

```
class Article extends Model implements Hashable
{
    use InteractsWithHashes;

    public function getHashableAttributes(): array
    {
        return ['title', 'content', 'author'];
    }

    // Article hash changes when replies change
    public function getHashCompositeDependencies(): array
    {
        return ['replies'];
    }

    public function replies()
    {
        return $this->hasMany(Reply::class);
    }
}

class Reply extends Model implements Hashable
{
    use InteractsWithHashes;

    public function getHashableAttributes(): array
    {
        return ['content', 'author'];
    }

    // No child dependencies
    public function getHashCompositeDependencies(): array
    {
        return [];
    }

    // Define parent relations to notify when this model changes
    public function getHashParentRelations(): array
    {
        return ['article']; // Notify article when reply changes
    }

    public function article()
    {
        return $this->belongsTo(Article::class);
    }
}
```

When a reply is created/updated/deleted, the parent article's hash automatically updates through the parent relation notification.

### Scope Filtering

[](#scope-filtering)

Control which records get hashed using scopes:

```
class WeatherStation extends Model implements Hashable
{
    use InteractsWithHashes;

    public function getHashableAttributes(): array
    {
        return ['name', 'location', 'status'];
    }

    // Only active stations in specific regions get hashed
    public function getHashableScope(): ?\Closure
    {
        return function ($query) {
            $query->where('status', 'active')
                  ->whereIn('location', ['Bayern', 'Berlin']);
        };
    }
}
```

**Important**: The system ensures that:

- Only models within their scope get hash records
- Dependencies are only created for models that are in scope
- When a model goes out of scope, its hash is soft-deleted

CLI Commands
------------

[](#cli-commands)

### Sync Command (Recommended)

[](#sync-command-recommended)

The simplest way to synchronize all hash records with a single command:

```
# Synchronize all hashes (auto-discover, detect, cleanup, and update)
php artisan change-detection:sync

# Preview changes without making modifications
php artisan change-detection:sync --dry-run

# Show detailed report of operations
php artisan change-detection:sync --report

# Limit processing per model (useful for large datasets)
php artisan change-detection:sync --limit=1000

# Hard delete orphaned and soft-deleted hashes from database
php artisan change-detection:sync --purge

# Sync specific models only
php artisan change-detection:sync --models="App\Models\User,App\Models\Post"
```

This command combines auto-discovery, change detection, orphan cleanup, and hash updates in one operation.

**Note about --purge option**: By default, the sync command soft-deletes hashes (marks them with a `deleted_at` timestamp) when models are deleted or go out of scope. Using the `--purge` flag will:

- Hard delete (completely remove) orphaned hashes where the model record no longer exists
- Hard delete any previously soft-deleted hashes (where `deleted_at` is not null)
- This permanently removes the hash records and their related data via cascade deletion

#### Model Discovery

[](#model-discovery)

The sync command automatically discovers models through multiple methods:

1. **Publisher Records**: Models registered via Publishers (supports models anywhere in your codebase)
2. **Laravel Morph Map**: Models registered in Laravel's morph map
3. **App\\Models Directory**: Fallback scanning of the standard models directory

Models with dependencies are processed in the correct order: dependencies first, then main models.

### Detect Changes Command

[](#detect-changes-command)

For fine-grained control over change detection:

```
# Auto-discover and check all hashable models
php artisan change-detection:detect --auto-discover

# Check specific models
php artisan change-detection:detect --models="App\Models\User,App\Models\Post"

# Update detected changes
php artisan change-detection:detect --auto-discover --update

# Clean up orphaned hashes
php artisan change-detection:detect --auto-discover --cleanup

# Show detailed report
php artisan change-detection:detect --auto-discover --report

# Limit processing (useful for large datasets)
php artisan change-detection:detect --auto-discover --limit=1000
```

### Truncate Command

[](#truncate-command)

Reset the change detection system by clearing all tables:

```
# Truncate all change detection tables
php artisan change-detection:truncate

# Skip confirmation prompt
php artisan change-detection:truncate --force

# Only truncate specific tables
php artisan change-detection:truncate --only=hashes,publishes
```

### Purge Deleted Hashes

[](#purge-deleted-hashes)

Remove soft-deleted hash records:

```
# Purge all deleted hashes
php artisan change-detection:purge

# Purge hashes deleted more than 7 days ago
php artisan change-detection:purge --older-than=7

# Preview what would be purged
php artisan change-detection:purge --dry-run

# Skip confirmation
php artisan change-detection:purge --force
```

### Background Processing

[](#background-processing)

Queue change detection for large datasets:

```
use Ameax\LaravelChangeDetection\Jobs\DetectChangesJob;

// Queue change detection for a specific model
DetectChangesJob::dispatch(
    modelClass: User::class,
    updateHashes: true,
    cleanupOrphaned: true,
    limit: 5000
);
```

Bulk Operations
---------------

[](#bulk-operations)

For high-performance scenarios with 100k+ records:

```
use Ameax\LaravelChangeDetection\Services\BulkHashProcessor;
use Ameax\LaravelChangeDetection\Services\ChangeDetector;

$processor = app(BulkHashProcessor::class);
$detector = app(ChangeDetector::class);

// Count changed models efficiently
$changedCount = $detector->countChangedModels(User::class);

// Process changes in batches (default: 1000 records per batch)
$updatedCount = $processor->processChangedModels(User::class, limit: 10000);

// Process specific model IDs
$modelIds = [1, 2, 3, 4, 5];
$updatedCount = $processor->updateHashesForIds(User::class, $modelIds);

// Build pending dependencies for models
$pendingCount = $processor->buildPendingDependencies(User::class);
```

### Performance Optimizations

[](#performance-optimizations)

The package is optimized for large-scale data processing:

- **Batch Processing**: All operations work in configurable chunks (default: 1000 records)
- **Bulk SQL Operations**: Uses `INSERT ... ON DUPLICATE KEY UPDATE` for efficient hash updates
- **Smart Dependency Building**: Dependencies are built after initial hash creation using the `has_dependencies_built` flag
- **N+1 Query Prevention**: Batch updates for dependency flags to avoid multiple queries
- **Optimized Model Discovery**: Models are discovered via Publishers and Laravel's Morph Map

Orphaned Hash Cleanup
---------------------

[](#orphaned-hash-cleanup)

Automatically detect and clean up hashes for deleted models:

```
use Ameax\LaravelChangeDetection\Services\OrphanedHashDetector;

$detector = app(OrphanedHashDetector::class);

// Count orphaned hashes
$orphanedCount = $detector->countOrphanedHashes(User::class);

// Get orphaned hash details
$orphanedHashes = $detector->detectOrphanedHashes(User::class);

// Clean up orphaned hashes
$cleanedCount = $detector->cleanupOrphanedHashes(User::class);

// Handle soft-deleted models
$softDeletedCount = $detector->cleanupSoftDeletedModelHashes(User::class);
```

Cross-Database Support
----------------------

[](#cross-database-support)

Store hash tables in a different database than your models:

```
// config/change-detection.php
return [
    'database_connection' => 'analytics', // Use different connection
    // ... other config
];

// database/config.php
'connections' => [
    'mysql' => [
        // Your main database
        'database' => 'main_app',
    ],
    'analytics' => [
        // Separate database for hash storage
        'driver' => 'mysql',
        'database' => 'analytics_db',
        'host' => 'analytics-server',
        // ... connection details
    ],
],
```

Publishing System
-----------------

[](#publishing-system)

### Publisher Configuration

[](#publisher-configuration)

Publishers implement the `Publisher` contract with comprehensive rate limiting and error handling configuration:

```
use Ameax\LaravelChangeDetection\Contracts\Publisher;

class CustomPublisher implements Publisher
{
    // Rate limiting configuration
    public function getBatchSize(): int
    {
        return 1000; // Process 1000 records per batch
    }

    public function getDelayMs(): int
    {
        return 100; // 100ms delay between each record
    }

    public function getRetryIntervals(): array
    {
        return [30, 300, 3600]; // Retry after 30s, 5m, 1h
    }

    // Error handling configuration
    public function getMaxValidationErrors(): int
    {
        return 50; // Stop job after 50 validation errors
    }

    public function getMaxInfrastructureErrors(): int
    {
        return 1; // Stop job after 1 infrastructure error
    }

    public function handlePublishException(\Throwable $exception): string
    {
        // Return 'stop_job', 'fail_record', or 'defer_record'
        if (str_contains($exception->getMessage(), 'Connection refused')) {
            return 'stop_job'; // Critical infrastructure issue
        }

        return 'defer_record'; // Retry later
    }

    // Publishing logic
    public function shouldPublish($model): bool
    {
        return $model->status === 'active';
    }

    public function getData($model): array
    {
        return $model->toArray();
    }

    public function publish($model, array $data): bool
    {
        // Your publishing logic here
        return $this->sendToExternalAPI($data);
    }
}
```

### Error Handling &amp; Recovery

[](#error-handling--recovery)

The publishing system tracks errors with detailed categorization:

```
// Check publish status for a model
$user = User::find(1);
$status = $user->getPublishStatus();

// Returns:
// [
//     'publisher_name' => [
//         'status' => 'failed|deferred|completed',
//         'last_error' => 'Error message',
//         'error_type' => 'validation|infrastructure|data',
//         'response_code' => 404,
//         'attempt_count' => 3,
//         'last_attempted_at' => '2024-01-15 10:30:00'
//     ]
// ]

// Reset errors for debugging
$user->resetPublishErrors(); // Reset all publishers
$user->resetPublishErrorsForPublisher($publisherId); // Reset specific publisher

// Get error count
$errorCount = $user->getPublishErrorCount();
```

### Processing Publishes

[](#processing-publishes)

The `BulkPublishJob` processes publishes with intelligent error handling:

```
# Queue bulk publish job (recommended)
php artisan change-detection:process-publishes

# Process synchronously with real-time feedback
php artisan change-detection:process-publishes --sync

# Force processing even if another job is running
php artisan change-detection:process-publishes --force

# Process specific number of records
php artisan change-detection:process-publishes --limit=500
```

The job automatically:

- **Single Instance**: Only one bulk job runs at a time using Laravel's `ShouldBeUnique`
- **Publisher Grouping**: Groups records by publisher for optimal batch processing
- **Rate Limiting**: Uses each publisher's `getBatchSize()` and `getDelayMs()` settings
- **Error Differentiation**: Tracks validation vs infrastructure errors separately
- **Smart Stopping**: Stops processing when error thresholds are reached
- **Response Tracking**: Captures HTTP response codes for debugging
- **Automatic Chaining**: Dispatches next batch if more records exist

### Publisher Examples

[](#publisher-examples)

**API Publisher (with strict rate limiting)**:

```
class ApiPublisher implements Publisher
{
    public function getBatchSize(): int { return 50; }
    public function getDelayMs(): int { return 200; }
    public function getMaxValidationErrors(): int { return 10; }
    public function getMaxInfrastructureErrors(): int { return 1; }

    public function handlePublishException(\Throwable $exception): string
    {
        if (str_contains($exception->getMessage(), '429')) {
            return 'stop_job'; // Rate limit hit, stop job
        }
        return 'defer_record';
    }
}
```

**SFTP Export Publisher (no limits)**:

```
class SftpExportPublisher implements Publisher
{
    public function getBatchSize(): int { return 0; }      // Unlimited
    public function getDelayMs(): int { return 0; }        // No delay
    public function getMaxValidationErrors(): int { return 0; } // No limit
    public function getMaxInfrastructureErrors(): int { return 1; } // Stop on connection issues
}
```

**Email Publisher (moderate batching)**:

```
class EmailPublisher implements Publisher
{
    public function getBatchSize(): int { return 100; }
    public function getDelayMs(): int { return 50; }
    public function getMaxValidationErrors(): int { return 20; } // Invalid emails
    public function getMaxInfrastructureErrors(): int { return 3; } // SMTP issues
}
```

### Immediate Publishing

[](#immediate-publishing)

For urgent records that bypass the queue system:

```
// Publish immediately (synchronous)
$success = $publishRecord->publishNow();

// Smart publishing - immediate if no bulk job running, else queue
$publishRecord->publishImmediatelyOrQueue();

// Model-level immediate publishing
$user = User::find(1);
$user->publishImmediately(); // Publishes to all configured publishers
```

### Error Types &amp; Response Codes

[](#error-types--response-codes)

The system categorizes errors for targeted handling:

- **Validation Errors**: Invalid data format, missing required fields
- **Infrastructure Errors**: Network timeouts, connection failures, authentication
- **Data Errors**: Missing models, empty data sets

Response codes are captured for HTTP-based publishers:

```
// Publish records store detailed error information
$publishRecord = Publish::find(1);
echo $publishRecord->last_error_message;     // "HTTP 404: Resource not found"
echo $publishRecord->last_response_code;     // 404
echo $publishRecord->error_type;             // "validation"
echo $publishRecord->attempt_count;          // 3
```

### Bulk Processing Details

[](#bulk-processing-details)

The `BulkPublishJob` processing flow:

1. **Acquire Lock**: Prevents multiple instances using cache locks
2. **Group by Publisher**: Processes each publisher type separately
3. **Load Settings**: Uses publisher-specific batch size and delays
4. **Process Batch**: Handles records with comprehensive error tracking
5. **Error Monitoring**: Stops when error thresholds are exceeded
6. **Chain Next Batch**: Automatically dispatches follow-up jobs
7. **Release Lock**: Ensures clean shutdown

Development &amp; Debugging
---------------------------

[](#development--debugging)

### LogPublisher for Development

[](#logpublisher-for-development)

```
use Ameax\LaravelChangeDetection\Publishers\LogPublisher;

// Configure log publisher
$publisher = new LogPublisher(
    logChannel: 'change-detection',
    logLevel: 'debug',
    includeHashData: true
);

// Get model data for debugging
$data = $publisher->getData($user);

// Publish to logs
$publisher->publish($user, $data);
```

### Hash Information Methods

[](#hash-information-methods)

```
// Get current hash record
$hashRecord = $user->getCurrentHash();

// Get hash dependents (models that depend on this one)
$dependents = $user->getHashDependents();

// Get publish records for this hash
$publishes = $user->getHashPublishes();

// Check if hash is deleted
$isDeleted = $user->isHashDeleted();

// Get last hash update time
$lastUpdated = $user->getHashLastUpdated();

// Calculate hashes without storing
$attributeHash = $user->calculateAttributeHash();
$compositeHash = $user->calculateCompositeHash();
```

### Error Management Methods

[](#error-management-methods)

```
// Reset all publish errors for this model
$resetCount = $user->resetPublishErrors();

// Reset errors for specific publisher
$resetCount = $user->resetPublishErrorsForPublisher($publisherId);

// Get total error count across all publishers
$errorCount = $user->getPublishErrorCount();

// Get detailed publish status for all publishers
$status = $user->getPublishStatus();
// Returns array with publisher status details

// Model can be published immediately (bypassing queue)
$user->publishImmediately();
```

Performance Considerations
--------------------------

[](#performance-considerations)

### MySQL Optimization

[](#mysql-optimization)

This package is optimized for MySQL with direct SQL queries:

- Hash calculations use MySQL's native `MD5()` and `SHA2()` functions
- Bulk operations use `INSERT...ON DUPLICATE KEY UPDATE` for efficiency
- Cross-database JOINs are properly qualified
- Configurable batch sizes for memory management

### Large Dataset Recommendations

[](#large-dataset-recommendations)

For applications with 100k+ records:

```
// Use bulk operations instead of individual model methods
$processor = app(BulkHashProcessor::class);
$processor->setBatchSize(5000); // Adjust based on memory

// Use background jobs for large operations
DetectChangesJob::dispatch(User::class, limit: 10000);

// Use limits when detecting changes
php artisan change-detection:detect --auto-discover --limit=5000
```

Common Use Cases
----------------

[](#common-use-cases)

### 1. Cache Invalidation

[](#1-cache-invalidation)

```
class ProductController extends Controller
{
    public function show(Product $product)
    {
        $cacheKey = "product.{$product->id}.{$product->getCurrentHash()->composite_hash}";

        return Cache::remember($cacheKey, 3600, function () use ($product) {
            return $this->generateProductView($product);
        });
    }
}
```

### 2. External System Synchronization

[](#2-external-system-synchronization)

```
// Detect changed products for API sync
$changedProducts = app(ChangeDetector::class)
    ->detectChangedModels(Product::class, limit: 100);

foreach ($changedProducts as $product) {
    // Sync to external system
    $this->syncToExternalAPI($product);

    // Update hash after successful sync
    $product->updateHash();
}
```

### 3. Audit Trail Integration

[](#3-audit-trail-integration)

```
class User extends Model implements Hashable
{
    use InteractsWithHashes;

    protected static function booted()
    {
        static::updated(function ($user) {
            if ($user->hasHashChanged()) {
                AuditLog::create([
                    'model_type' => 'user',
                    'model_id' => $user->id,
                    'old_hash' => $user->getOriginal('hash'),
                    'new_hash' => $user->getCurrentHash()->composite_hash,
                    'changed_at' => now(),
                ]);
            }
        });
    }
}
```

### 4. Data Integrity Monitoring

[](#4-data-integrity-monitoring)

```
// Daily integrity check
$command = Artisan::call('change-detection:detect', [
    '--auto-discover' => true,
    '--report' => true,
]);

// Queue periodic cleanup
Schedule::job(new DetectChangesJob(User::class, false, true))
    ->daily()
    ->description('Clean up orphaned user hashes');
```

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run static analysis:

```
composer analyse
```

Format code:

```
composer format
```

The package includes comprehensive tests for:

- Hash calculation and storage
- Composite dependency tracking
- Bulk operations and performance
- Cross-database functionality
- CLI commands and background jobs
- Change detection algorithms

Requirements
------------

[](#requirements)

- **PHP**: 8.3+
- **Laravel**: 12.0+
- **Database**: MySQL 8.0+ (optimized for MySQL)
- **Extensions**: PDO MySQL

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Architecture
------------

[](#architecture)

The package follows clean architecture principles:

- **Services**: Core business logic (ChangeDetector, BulkHashProcessor, etc.)
- **Models**: Database entities with proper relationships
- **Contracts**: Interfaces for extensibility
- **Traits**: Reusable functionality for models
- **Commands**: CLI tools for management
- **Jobs**: Background processing capabilities

All classes are kept under 200 lines for maintainability, and the codebase follows PSR-12 coding standards with comprehensive type safety.

Credits
-------

[](#credits)

- [Michael Schmidt](https://github.com/69188126+ms-aranes)
- Built with [Spatie Laravel Package Tools](https://github.com/spatie/laravel-package-tools)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance58

Moderate activity, may be stable

Popularity16

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity20

Early-stage or recently created project

 Bus Factor1

Top contributor holds 96.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/f67be86f330b5a3e644c8594bbf322be6ab1a08da5434d9cef417097d5567287?d=identicon)[aranes](/maintainers/aranes)

---

Top Contributors

[![ms-aranes](https://avatars.githubusercontent.com/u/69188126?v=4)](https://github.com/ms-aranes "ms-aranes (75 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")[![Hadi-Ara](https://avatars.githubusercontent.com/u/232357222?v=4)](https://github.com/Hadi-Ara "Hadi-Ara (1 commits)")

### Embed Badge

![Health badge](/badges/ameax-laravel-change-detection/health.svg)

```
[![Health](https://phpackages.com/badges/ameax-laravel-change-detection/health.svg)](https://phpackages.com/packages/ameax-laravel-change-detection)
```

###  Alternatives

[codingfreaks/cf-cookiemanager

Manage cookies, scripts, and GDPR compliance on your Typo3 website with CodingFreaks Typo3 Cookie Manager. Customize cookie banners, streamline workflow, and enhance user experience. Ensure GDPR compliance and take control of cookie management with our Typo3 cookie management extension. Visit the official Typo3 Documentation page to learn more.

1625.8k](/packages/codingfreaks-cf-cookiemanager)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
