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.
Installation:
composer require miljar/php-exif
Publish the config (if needed):
php artisan vendor:publish --tag=exif-config
Basic Usage:
use Miljar\PhpExif\Exif;
$exif = new Exif(storage_path('app/image.jpg'));
$data = $exif->getData();
// Check if EXIF data exists
if ($data !== false) {
dd($data['EXIF']['DateTimeOriginal']); // Output: "2023:05:15 14:30:00"
}
First Use Case: Extract GPS coordinates from an uploaded image:
$exif = new Exif($request->file('photo')->path());
$gps = $exif->getGPS();
if ($gps) {
$latitude = $gps['Latitude'];
$longitude = $gps['Longitude'];
// Store in database or use for geocoding
}
config/exif.php (check fallback_to_exiftool and exiftool_path).// In a controller or service
public function processImage($imagePath)
{
$exif = new Exif($imagePath);
$metadata = $exif->getData();
if ($metadata !== false) {
// Normalized data (e.g., ExposureTime in seconds)
$exposureTime = $metadata['EXIF']['ExposureTime'] ?? null;
$this->saveMetadata($exposureTime);
}
}
// app/Jobs/ExtractExifJob.php
public function handle()
{
$image = Image::find($this->imageId);
$exif = new Exif($image->path);
$data = $exif->getData();
if ($data !== false) {
$image->update(['exif_data' => json_encode($data)]);
}
}
// Dispatch job
ExtractExifJob::dispatch($image);
Configure in config/exif.php:
'fallback_to_exiftool' => env('EXIF_USE_EXIFTOOL', true),
'exiftool_path' => '/usr/local/bin/exiftool',
Usage remains identical—library handles the fallback automatically.
Service Provider:
// app/Providers/ExifServiceProvider.php
public function register()
{
$this->app->singleton(Exif::class, function ($app) {
return new Exif(config('exif.default_path'));
});
}
Model Observers:
// app/Observers/ImageObserver.php
public function saved(Image $image)
{
$exif = app(Exif::class)->setPath($image->path)->getData();
if ($exif) $image->exif_data = json_encode($exif);
}
Artisan Command:
// app/Console/Commands/ExtractAllExif.php
public function handle()
{
$images = Image::all();
foreach ($images as $image) {
$exif = new Exif($image->path);
$image->update(['exif_data' => $exif->getData()]);
}
}
Leverage the library’s built-in normalization (e.g., ExposureTime converted to seconds):
$data = $exif->getData();
$normalizedExposure = $data['EXIF']['ExposureTime'] ?? 0; // Returns float (e.g., 0.004 for 1/250s)
Extract and parse GPS coordinates:
$gps = $exif->getGPS();
if ($gps) {
$latitude = $gps['Latitude'] . ' ' . $gps['LatitudeRef'];
$longitude = $gps['Longitude'] . ' ' . $gps['LongitudeRef'];
// Use with Laravel Geocoding or Google Maps API
}
False Positives/Negatives:
getData() returns false if no EXIF data exists (not null or an empty array).if ($data !== false).$data = $exif->getData();
if ($data === false) {
Log::warning('No EXIF data found in', ['path' => $imagePath]);
}
Exiftool Dependency:
fallback_to_exiftool is true but exiftool is missing, the library throws a RuntimeException.fallback_to_exiftool to false or ensure exiftool is installed/accessible.exiftool to your Dockerfile:
RUN apt-get update && apt-get install -y libimage-exiftool-perl
Encoding Issues:
$rawData = $exif->getRawData();
$decoded = mb_convert_encoding($rawData, 'UTF-8', 'auto');
GPS Zero-Degree Coordinates:
0/1 for GPS coordinates (e.g., Latitude=0/1 means 0° 0' 0").getGPS() method, which normalizes this.Memory Limits:
foreach (Image::chunk(100) as $chunk) {
foreach ($chunk as $image) {
$exif = new Exif($image->path);
// ...
}
}
Inspect Raw Data:
$rawData = $exif->getRawData();
dd($rawData); // Debug unnormalized EXIF/IPTC data
Check Exiftool Output: Run manually to compare:
exiftool -json image.jpg
Log Fallbacks:
try {
$data = $exif->getData();
} catch (\RuntimeException $e) {
Log::error('EXIF fallback failed: ' . $e->getMessage());
// Fallback to native PHP
$data = exif_read_data($imagePath);
}
Validate GPS: Ensure GPS data is valid before processing:
$gps = $exif->getGPS();
if (!$gps || empty($gps['Latitude'])) {
Log::warning('Invalid GPS data in', ['path' => $imagePath]);
}
Custom Data Mappers: Extend the library’s mapping logic (see v0.4.0 changes):
// app/Services/CustomExifMapper.php
use Miljar\PhpExif\Mapping\ExifMapper;
class CustomExifMapper extends ExifMapper
{
protected function mapExposureTime($value)
{
return (float) $value; // Custom normalization
}
}
Add New Tag Support:
Extend the Exif class to handle unsupported tags:
// app/Services/ExtendedExif.php
class ExtendedExif extends \Miljar\PhpExif\Exif
{
public function getCustomTag($tagName)
{
$raw = $this->getRawData();
return $raw['CustomTags'][$tagName] ?? null;
}
}
Queue Job for Async Processing:
// app/Jobs/ExtractEx
How can I help you explore Laravel packages today?