miljar/php-exif
PHPExif is a PHP library for reading image metadata (EXIF/IPTC) through a clean, standard API. It wraps native PHP functions like exif_read_data and iptcparse, and supports the ExifTool CLI adapter for broader metadata extraction.
ExifExtracted) for downstream actions like geocoding, tagging, or analytics.GPSLatitude) integrates cleanly with Eloquent models or Scout for search.config/app.php with minimal setup:
'providers' => [
Miljar\PhpExif\ExifServiceProvider::class,
],
Exif class via Laravel’s container for testability:
$this->app->bind(Miljar\PhpExif\Exif::class, function ($app) {
return new Miljar\PhpExif\Exif($app['path.storage'] . '/images/photo.jpg');
});
php artisan exif:process-folder).phpunit/phpunit:^9.5 and pestphp/pest:^1.0 for compatibility.Process facade with escaped arguments:
Process::of([$exiftoolPath, '-json', $filePath])->run();
exiftool --extensive).exif_read_data and alternatives like spatie/image for 10K+ images.null on Exiftool failure) or log and retry?Exif class for unit tests; use real images in integration tests (e.g., from ExifTestImages).Storage facade for cloud-agnostic processing (S3, local, etc.).ShouldQueue jobs for async extraction (e.g., ExtractExifJob with handle() method).ExifExtracted events to notify listeners (e.g., geocoding service):
event(new ExifExtracted($image, $metadata));
FROM --platform=linux alpine/exiftool) for consistency.exiftool --extensive for accuracy.ExifRepository) to abstract extraction logic.Redis) for frequently accessed metadata.fileinfo and exif extensions are enabled in php.ini:
extension=fileinfo
extension=exif
try {
$exif = new Miljar\PhpExif\Exif($filePath);
return $exif->getData() ?: [];
} catch (\RuntimeException $e) {
Log::warning("EXIF extraction failed: {$e->getMessage()}");
return [];
}
composer require miljar/php-exif
php artisan vendor:publish --tag=exif-config
// app/Facades/Exif.php
public static function extract(string $path): array {
return app(ExifRepository::class)->extract($path);
}
Image::saved()) or API routes:
Route::post('/images', function (Request $request) {
$image = Image::create($request->file('image')->store('images'));
Exif::extract($image->path)->then(fn($metadata) => $image->update(['metadata' => $metadata]));
});
exif_read_data changes).spatie/image by 2025.exiftool version in Docker (e.g., alpine/exiftool:12.60).jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.1', '8.2', '8.3']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- run: composer install && composer test
Log::debug('Raw EXIF data', ['data' => $exif->getRawData()]);
EXIFTOOL_PATH in .env.chmod +x /usr/local/bin/exiftool.supervisor) for concurrent EXIF extraction.$exif = new Miljar\PhpExif\Exif($filePath, ['stream' => true]);
GPSLatitude) for fast queries.|
How can I help you explore Laravel packages today?