Installation:
composer require typhoon/change-detector
Basic Usage:
use Typhoon\ChangeDetector\ChangeDetectors;
use Typhoon\ChangeDetector\FileChangeDetector;
// Detect file changes (e.g., config file)
$detector = FileChangeDetector::fromFile('/path/to/config/app.php');
if ($detector->changed()) {
// Trigger cache rebuild or other actions
Artisan::call('config:clear');
}
// Detect PHP version changes
$phpDetector = ChangeDetectors::phpVersion('8.1.0');
if ($phpDetector->changed()) {
Log::warning('PHP version changed; validate compatibility.');
}
First Use Case:
bootstrap/cache/config.php before serving cached responses:
$cacheDetector = FileChangeDetector::fromFile(base_path('bootstrap/cache/config.php'));
if ($cacheDetector->changed()) {
Cache::forget('config');
}
src/ChangeDetector/ for available detectors:
FileChangeDetector (filesystem changes)PhpVersionChangeDetector (PHP version shifts)ComposerPackageChangeDetector (Composer dependency updates)ConstantChangeDetector (runtime constant values)ChangeDetectors::from() for combining detectors with a default fallback.// Single file
$detector = FileChangeDetector::fromFile('/path/to/file.php');
if ($detector->changed()) {
// Handle change
}
// Multiple files with ignore patterns
$detector = FileChangeDetector::fromFiles([
'config/app.php',
'routes/web.php',
])->ignore('vendor/**', 'node_modules/**');
// Track a specific package version
$detector = ChangeDetectors::composerPackage('laravel/framework', '^10.0');
if ($detector->changed()) {
Log::alert('Laravel framework version changed!');
}
// Track all dev dependencies
$detector = ChangeDetectors::composerPackage('*', '*', true); // dev-only
// Detect PHP version changes
$detector = ChangeDetectors::phpVersion('8.1.0');
if ($detector->changed()) {
throw new RuntimeException('PHP version mismatch detected.');
}
// Detect extension presence
$detector = ChangeDetectors::phpExtension('redis');
if (!$detector->exists()) {
throw new RuntimeException('Redis extension required.');
}
// Check config file OR PHP version
$detector = ChangeDetectors::from([
FileChangeDetector::fromFile('config/app.php'),
ChangeDetectors::phpVersion('8.1.0'),
], false); // Return true if ANY detector changes
if ($detector->changed()) {
Artisan::call('optimize:clear');
}
// app/Providers/ChangeDetectorServiceProvider.php
use Illuminate\Support\Facades\Event;
use Typhoon\ChangeDetector\FileChangeDetector;
public function boot(): void
{
Event::listen('filesystem.updated', function ($event) {
$detector = FileChangeDetector::fromFile($event->path);
if ($detector->changed()) {
Cache::forget('config');
}
});
}
// app/Console/Commands/CheckChanges.php
use Typhoon\ChangeDetector\ChangeDetectors;
protected $signature = 'change:detect {--check= : Comma-separated checks (file,php,composer)}';
public function handle()
{
$checks = explode(',', $this->option('check'));
if (in_array('file', $checks)) {
$this->checkFiles();
}
if (in_array('php', $checks)) {
$this->checkPhpVersion();
}
}
private function checkFiles(): void
{
$detector = FileChangeDetector::fromFiles([
'config/app.php',
'routes/web.php',
]);
if ($detector->changed()) {
$this->info('Files changed. Clearing cache...');
Artisan::call('config:clear');
}
}
// config/app.php
'providers' => [
// ...
Typhoon\ChangeDetector\ChangeDetectorServiceProvider::class,
],
// Register detectors as singletons
'change_detectors' => [
'config' => \Typhoon\ChangeDetector\FileChangeDetector::fromFile(config_path('app.php')),
'routes' => \Typhoon\ChangeDetector\FileChangeDetector::fromFile(base_path('routes/web.php')),
],
# .github/workflows/change-detection.yml
jobs:
detect-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-php@v3
with:
php-version: '8.1'
- run: composer install
- run: |
php artisan change:detect --check=file,composer
if [ $? -eq 1 ]; then
echo "Changes detected! Triggering rebuild..."
# Add your rebuild logic here
fi
use Typhoon\ChangeDetector\ChangeDetector;
class DatabaseSchemaDetector implements ChangeDetector
{
public function changed(): bool
{
$currentSchema = $this->getCurrentSchemaHash();
return $currentSchema !== cache('schema_hash');
}
private function getCurrentSchemaHash(): string
{
// Implement logic to generate a hash of your schema
return hash('sha256', Schema::getRawSchema());
}
}
// Register in ChangeDetectors::from()
$detector = ChangeDetectors::from([
new DatabaseSchemaDetector(),
], false);
use Typhoon\ChangeDetector\CompositeChangeDetector;
$composite = new CompositeChangeDetector([
FileChangeDetector::fromFile('config/app.php'),
ChangeDetectors::phpVersion('8.1.0'),
ChangeDetectors::composerPackage('laravel/framework', '^10.0'),
]);
if ($composite->changed()) {
// Handle any change
}
Inverted Logic in Version Detectors
0.4.3 and 0.4.4 fixed inverted logic in PhpVersionChangeDetector and PhpExtensionVersionChangeDetector. Always verify changed() returns true when expected.$detector = ChangeDetectors::phpVersion('8.0.0');
$detector->changed(); // Should return true if PHP >= 8.1.0
Filesystem Permissions
FileChangeDetector may fail silently if it lacks read permissions for a file.File::exists() first:
if (!File::exists($path)) {
throw new RuntimeException("File not found: {$path}");
}
Composer Lock File Changes
ComposerPackageChangeDetector may trigger false positives during composer install if composer.lock is updated but dependencies haven’t changed.composer.json + composer.lock hashes:
$jsonHash = hash_file('sha256', 'composer.json');
$lockHash = hash_file('sha256', 'composer.lock');
if ($jsonHash !== cache('composer_json_hash') || $lockHash !== cache('composer_lock_hash')) {
// Changes detected
}
Time-Based Optimizations
mtime checks in FileChangeDetector (via withoutMtime()) may lead to missed changes if files are modified but mtime isn’t updated (e.g., symlinks).$detector = FileChangeDetector::fromFile($path)
->withoutMtime()
->withContentHash();
PHP Constants Caching
ConstantChangeDetector may return stale results if constants are defined in included files that aren’t re-evaluated.How can I help you explore Laravel packages today?