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

Maintenance59

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

[shpasser/gae-support-l5

Google App Engine Support for Laravel 5.1 apps.

15915.4k](/packages/shpasser-gae-support-l5)

PHPackages © 2026

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