diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eb09883..2059618 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,15 @@ CHANGELOG ===== +0.4.0 +----- + +* Improvement `#25`_: Extracted mapping logic into separate classes +* Added ``GPS`` information +* Improved unit tests; coverage up to 100% +* Added contributor to README.md +* Added extra stuff about unit tests to the ``Contributing`` section of the README.md + 0.3.0 ----- @@ -12,6 +21,7 @@ CHANGELOG * Composer.json: added semver version for phpmd; removed pdepend * added ``Orientation``, ``MimeType``, ``FileSize`` and ``ColorSpace`` options to EXIF +.. _`#25`: https://github.com/Miljar/php-exif/issues/25 .. _`#24`: https://github.com/Miljar/php-exif/issues/24 .. _`#18`: https://github.com/Miljar/php-exif/issues/18 .. _`#15`: https://github.com/Miljar/php-exif/issues/15 diff --git a/README.md b/README.md index 5ab0451..fbcf662 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [PHPExif v0.3.0](http://github.com/Miljar/php-exif) [![Build Status](https://travis-ci.org/Miljar/php-exif.png?branch=master)](https://travis-ci.org/Miljar/php-exif) [![Coverage Status](https://coveralls.io/repos/Miljar/php-exif/badge.svg?branch=master)](https://coveralls.io/r/Miljar/php-exif?branch=master) [![Code Climate](https://codeclimate.com/github/Miljar/php-exif/badges/gpa.svg)](https://codeclimate.com/github/Miljar/php-exif) +# [PHPExif v0.4.0](http://github.com/Miljar/php-exif) [![Build Status](https://travis-ci.org/Miljar/php-exif.png?branch=master)](https://travis-ci.org/Miljar/php-exif) [![Coverage Status](https://coveralls.io/repos/Miljar/php-exif/badge.svg?branch=master)](https://coveralls.io/r/Miljar/php-exif?branch=master) [![Code Climate](https://codeclimate.com/github/Miljar/php-exif/badges/gpa.svg)](https://codeclimate.com/github/Miljar/php-exif) PHPExif is a library which gives you easy access to the EXIF meta-data of an image. @@ -12,7 +12,7 @@ PHPExif serves as a wrapper around some native or CLI tools which access this EX ## Installation (composer) ```json -"miljar/php-exif": "~0.3.0" +"miljar/php-exif": "~0.4.0" ``` @@ -27,6 +27,7 @@ PHPExif serves as a wrapper around some native or CLI tools which access this EX Please submit all pull requests against the correct branch. The release branch for the next version is a branch with the same name as the next version. Bugfixes should go in the master branch, unless they are for code in a new release branch. PHPExif is written according the [PSR-0/1/2 standards](http://www.php-fig.org/). When submitting code, please make sure it is conform these standards. +We aim to have all functionality covered by unit tests. When submitting code, you are strongly encouraged to unit test your code and to keep the level of code coverage on par with the current level. All contributions are welcomed and greatly appreciated. @@ -40,6 +41,7 @@ Have a bug or a feature request? [Please open a new issue](https://github.com/Mi * [Ingewikkeld](https://github.com/Ingewikkeld) * [Christophe Singer](https://github.com/wasinger) * [Hanov Ruslan](https://github.com/hanovruslan) +* [Julian Gutierrez](https://github.com/juliangut) ## License diff --git a/lib/PHPExif/Adapter/AdapterAbstract.php b/lib/PHPExif/Adapter/AdapterAbstract.php index 1e4450f..9e0315a 100644 --- a/lib/PHPExif/Adapter/AdapterAbstract.php +++ b/lib/PHPExif/Adapter/AdapterAbstract.php @@ -11,6 +11,9 @@ namespace PHPExif\Adapter; +use PHPExif\Mapper\MapperInterface; +use PHPExif\Hydrator\HydratorInterface; + /** * PHP Exif Reader Adapter Abstract * @@ -21,10 +24,25 @@ */ abstract class AdapterAbstract implements AdapterInterface { + /** + * @var string + */ + protected $hydratorClass = '\\PHPExif\\Hydrator\\Mutator'; + + /** + * @var \PHPExif\Mapper\MapperInterface + */ + protected $mapper; + + /** + * @var \PHPExif\Hydrator\HydratorInterface + */ + protected $hydrator; + /** * Class constructor * - * @param array $data Optional array of data to initialize the object with + * @param array $options Optional array of data to initialize the object with */ public function __construct(array $options = array()) { @@ -34,88 +52,76 @@ public function __construct(array $options = array()) } /** - * Set array of options in the current object + * Mutator for the data mapper * - * @param array $options - * @return \PHPExif\Reader\AdapterAbstract + * @param \PHPExif\Mapper\MapperInterface $mapper + * @return \PHPExif\Adapter\AdapterInterface */ - public function setOptions(array $options) + public function setMapper(MapperInterface $mapper) { - foreach ($options as $property => $value) { - $setter = $this->determinePropertySetter($property); - if (method_exists($this, $setter)) { - $this->$setter($value); - } - } + $this->mapper = $mapper; return $this; } /** - * Detemines the name of the getter method for given property name + * Accessor for the data mapper * - * @param string $property The property to determine the getter for - * @return string The name of the getter method + * @return \PHPExif\Mapper\MapperInterface */ - protected function determinePropertyGetter($property) + public function getMapper() { - $method = 'get' . ucfirst($property); - return $method; + if (null === $this->mapper) { + // lazy load one + $mapper = new $this->mapperClass; + + $this->setMapper($mapper); + } + + return $this->mapper; } /** - * Detemines the name of the setter method for given property name + * Mutator for the hydrator * - * @param string $property The property to determine the setter for - * @return string The name of the setter method + * @param \PHPExif\Hydrator\HydratorInterface $hydrator + * @return \PHPExif\Adapter\AdapterInterface */ - protected function determinePropertySetter($property) + public function setHydrator(HydratorInterface $hydrator) { - $method = 'set' . ucfirst($property); - return $method; + $this->hydrator = $hydrator; + + return $this; } /** - * Get a list of the class constants prefixed with given $type + * Accessor for the data hydrator * - * @param string $type - * @return array + * @return \PHPExif\Hydrator\HydratorInterface */ - public function getClassConstantsOfType($type) + public function getHydrator() { - $class = new \ReflectionClass(get_called_class()); - $constants = $class->getConstants(); - - $list = array(); - $type = strtoupper($type) . '_'; - foreach ($constants as $key => $value) { - if (strpos($key, $type) === 0) { - $list[$key] = $value; - } + if (null === $this->hydrator) { + // lazy load one + $hydrator = new $this->hydratorClass; + + $this->setHydrator($hydrator); } - return $list; + + return $this->hydrator; } /** - * Returns an array notation of current instance + * Set array of options in the current object * - * @return array + * @param array $options + * @return \PHPExif\Reader\AdapterAbstract */ - public function toArray() + public function setOptions(array $options) { - $rc = new \ReflectionClass(get_class($this)); - $properties = $rc->getProperties(); - $arrResult = array(); - - foreach ($properties as $rp) { - /* @var $rp \ReflectionProperty */ - $getter = $this->determinePropertyGetter($rp->getName()); - if (!method_exists($this, $getter)) { - continue; - } - $arrResult[$rp->getName()] = $this->$getter(); - } + $hydrator = $this->getHydrator(); + $hydrator->hydrate($this, $options); - return $arrResult; + return $this; } } diff --git a/lib/PHPExif/Adapter/AdapterInterface.php b/lib/PHPExif/Adapter/AdapterInterface.php index fc44802..780e77d 100644 --- a/lib/PHPExif/Adapter/AdapterInterface.php +++ b/lib/PHPExif/Adapter/AdapterInterface.php @@ -7,6 +7,7 @@ * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License * @category PHPExif * @package Reader + * @codeCoverageIgnore */ namespace PHPExif\Adapter; diff --git a/lib/PHPExif/Adapter/Exiftool.php b/lib/PHPExif/Adapter/Exiftool.php index 8e6bbe6..f4eb648 100644 --- a/lib/PHPExif/Adapter/Exiftool.php +++ b/lib/PHPExif/Adapter/Exiftool.php @@ -14,7 +14,6 @@ use PHPExif\Exif; use InvalidArgumentException; use RuntimeException; -use DateTime; /** * PHP Exif Exiftool Reader Adapter @@ -40,6 +39,11 @@ class Exiftool extends AdapterAbstract */ protected $numeric = true; + /** + * @var string + */ + protected $mapperClass = '\\PHPExif\\Mapper\\Exiftool'; + /** * Setter for the exiftool binary path * @@ -109,8 +113,16 @@ public function getExifFromFile($file) ); $data = json_decode($result, true); - $mappedData = $this->mapData(reset($data)); - $exif = new Exif($mappedData); + + // map the data: + $mapper = $this->getMapper(); + $mapper->setNumeric($this->numeric); + $mappedData = $mapper->mapRawData(reset($data)); + + // hydrate a new Exif object + $exif = new Exif(); + $hydrator = $this->getHydrator(); + $hydrator->hydrate($exif, $mappedData); $exif->setRawData(reset($data)); return $exif; @@ -148,96 +160,4 @@ protected function getCliOutput($command) return $result; } - - /** - * Maps native data to Exif format - * - * @param array $source - * @return array - */ - public function mapData(array $source) - { - $focalLength = false; - if (isset($source['FocalLength'])) { - $focalLengthParts = explode(' ', $source['FocalLength']); - $focalLength = (int) reset($focalLengthParts); - } - - $exposureTime = false; - if (isset($source['ExposureTime'])) { - $exposureTime = '1/' . round(1 / $source['ExposureTime']); - } - - $caption = false; - if (isset($source['Caption'])) { - $caption = $source['Caption']; - } elseif (isset($source['Caption-Abstract'])) { - $caption = $source['Caption-Abstract']; - } - - $gpsLocation = false; - if (isset($source['GPSLatitudeRef']) && isset($source['GPSLongitudeRef'])) { - $latitude = $this->extractGPSCoordinates($source['GPSLatitude']); - $longitude = $this->extractGPSCoordinates($source['GPSLongitude']); - - if ($latitude !== false && $longitude !== false) { - $gpsLocation = sprintf( - '%s,%s', - (strtoupper($source['GPSLatitudeRef'][0]) === 'S' ? -1 : 1) * $latitude, - (strtoupper($source['GPSLongitudeRef'][0]) === 'W' ? -1 : 1) * $longitude - ); - } - } - - return array( - Exif::APERTURE => (!isset($source['Aperture'])) ? - false : sprintf('f/%01.1f', $source['Aperture']), - Exif::AUTHOR => (!isset($source['Artist'])) ? false : $source['Artist'], - Exif::CAMERA => (!isset($source['Model'])) ? false : $source['Model'], - Exif::CAPTION => $caption, - Exif::COLORSPACE => (!isset($source[Exif::COLORSPACE]) ? false : $source[Exif::COLORSPACE]), - Exif::COPYRIGHT => (!isset($source['Copyright'])) ? false : $source['Copyright'], - Exif::CREATION_DATE => (!isset($source['CreateDate'])) ? - false : DateTime::createFromFormat('Y:m:d H:i:s', $source['CreateDate']), - Exif::CREDIT => (!isset($source['Credit'])) ? false : $source['Credit'], - Exif::EXPOSURE => $exposureTime, - Exif::FILESIZE => (!isset($source[Exif::FILESIZE]) ? false : $source[Exif::FILESIZE]), - Exif::FOCAL_LENGTH => $focalLength, - Exif::FOCAL_DISTANCE => (!isset($source['ApproximateFocusDistance'])) ? - false : sprintf('%1$sm', $source['ApproximateFocusDistance']), - Exif::HEADLINE => (!isset($source['Headline'])) ? false : $source['Headline'], - Exif::HEIGHT => (!isset($source['ImageHeight'])) ? false : $source['ImageHeight'], - Exif::HORIZONTAL_RESOLUTION => (!isset($source['XResolution'])) ? false : $source['XResolution'], - Exif::ISO => (!isset($source['ISO'])) ? false : $source['ISO'], - Exif::JOB_TITLE => (!isset($source['JobTitle'])) ? false : $source['JobTitle'], - Exif::KEYWORDS => (!isset($source['Keywords'])) ? false : $source['Keywords'], - Exif::MIMETYPE => (!isset($source['MIMEType'])) ? false : $source['MIMEType'], - Exif::ORIENTATION => (!isset($source['Orientation'])) ? false : $source['Orientation'], - Exif::SOFTWARE => (!isset($source['Software'])) ? false : $source['Software'], - Exif::SOURCE => (!isset($source['Source'])) ? false : $source['Source'], - Exif::TITLE => (!isset($source['Title'])) ? false : $source['Title'], - Exif::VERTICAL_RESOLUTION => (!isset($source['YResolution'])) ? false : $source['YResolution'], - Exif::WIDTH => (!isset($source['ImageWidth'])) ? false : $source['ImageWidth'], - Exif::GPS => $gpsLocation, - ); - } - - /** - * Extract GPS coordinates from formatted string - * - * @param string $coordinates - * @return array - */ - protected function extractGPSCoordinates($coordinates) - { - if ($this->numeric === true) { - return abs((float) $coordinates); - } else { - if (!preg_match('!^([0-9.]+) deg ([0-9.]+)\' ([0-9.]+)"!', $coordinates, $matches)) { - return false; - } - - return intval($matches[1]) + (intval($matches[2]) / 60) + (floatval($matches[3]) / 3600); - } - } } diff --git a/lib/PHPExif/Adapter/Native.php b/lib/PHPExif/Adapter/Native.php index a7d6756..c5405e9 100644 --- a/lib/PHPExif/Adapter/Native.php +++ b/lib/PHPExif/Adapter/Native.php @@ -60,6 +60,11 @@ class Native extends AdapterAbstract */ protected $sectionsAsArrays = self::SECTIONS_FLAT; + /** + * @var string + */ + protected $mapperClass = '\\PHPExif\\Mapper\\Native'; + /** * Contains the mapping of names to IPTC field numbers * @@ -189,8 +194,15 @@ public function getExifFromFile($file) $xmpData = $this->getIptcData($file); $data = array_merge($data, array(self::SECTION_IPTC => $xmpData)); - $mappedData = $this->mapData($data); - $exif = new Exif($mappedData); + + // map the data: + $mapper = $this->getMapper(); + $mappedData = $mapper->mapRawData($data); + + // hydrate a new Exif object + $exif = new Exif(); + $hydrator = $this->getHydrator(); + $hydrator->hydrate($exif, $mappedData); $exif->setRawData($data); return $exif; @@ -224,164 +236,4 @@ public function getIptcData($file) return $arrData; } - - /** - * Maps native data to Exif format - * - * @param array $source - * @return array - */ - public function mapData(array $source) - { - $focalLength = false; - if (isset($source['FocalLength'])) { - $parts = explode('/', $source['FocalLength']); - $focalLength = (int)reset($parts) / (int)end($parts); - } - - $horResolution = false; - if (isset($source['XResolution'])) { - $resolutionParts = explode('/', $source['XResolution']); - $horResolution = (int)reset($resolutionParts); - } - - $vertResolution = false; - if (isset($source['YResolution'])) { - $resolutionParts = explode('/', $source['YResolution']); - $vertResolution = (int)reset($resolutionParts); - } - - $exposureTime = false; - if (isset($source['ExposureTime'])) { - // normalize ExposureTime - // on one test image, it reported "10/300" instead of "1/30" - list($counter, $denominator) = explode('/', $source['ExposureTime']); - if (intval($counter) !== 1) { - $denominator /= $counter; - } - $exposureTime = '1/' . round($denominator); - } - - $gpsLocation = false; - if (isset($source['GPSLatitudeRef']) && isset($source['GPSLongitudeRef'])) { - $latitude = $this->extractGPSCoordinate($source['GPSLatitude']); - $longitude = $this->extractGPSCoordinate($source['GPSLongitude']); - - $gpsLocation = sprintf( - '%s,%s', - (strtoupper($source['GPSLatitudeRef'][0]) === 'S' ? -1 : 1) * $latitude, - (strtoupper($source['GPSLongitudeRef'][0]) === 'W' ? -1 : 1) * $longitude - ); - } - - return array( - Exif::APERTURE => (!isset($source[self::SECTION_COMPUTED]['ApertureFNumber'])) ? - false : $source[self::SECTION_COMPUTED]['ApertureFNumber'], - Exif::AUTHOR => (!isset($source['Artist'])) ? false : $source['Artist'], - Exif::CAMERA => (!isset($source['Model'])) ? false : $source['Model'], - Exif::CAPTION => (!isset($source[self::SECTION_IPTC]['caption'])) ? - false : $source[self::SECTION_IPTC]['caption'], - Exif::COLORSPACE => (!isset($source[Exif::COLORSPACE]) ? false : $source[Exif::COLORSPACE]), - Exif::COPYRIGHT => (!isset($source[self::SECTION_IPTC]['copyright'])) ? - false : $source[self::SECTION_IPTC]['copyright'], - Exif::CREATION_DATE => (!isset($source['DateTimeOriginal'])) ? - false : DateTime::createFromFormat('Y:m:d H:i:s', $source['DateTimeOriginal']), - Exif::CREDIT => (!isset($source[self::SECTION_IPTC]['credit'])) ? - false : $source[self::SECTION_IPTC]['credit'], - Exif::EXPOSURE => $exposureTime, - Exif::FILESIZE => (!isset($source[Exif::FILESIZE]) ? false : $source[Exif::FILESIZE]), - Exif::FOCAL_LENGTH => $focalLength, - Exif::FOCAL_DISTANCE => (!isset($source[self::SECTION_COMPUTED]['FocusDistance'])) ? - false : $source[self::SECTION_COMPUTED]['FocusDistance'], - Exif::HEADLINE => (!isset($source[self::SECTION_IPTC]['headline'])) ? - false : $source[self::SECTION_IPTC]['headline'], - Exif::HEIGHT => (!isset($source[self::SECTION_COMPUTED]['Height'])) ? - false : $source[self::SECTION_COMPUTED]['Height'], - Exif::HORIZONTAL_RESOLUTION => $horResolution, - Exif::ISO => (!isset($source['ISOSpeedRatings'])) ? false : $source['ISOSpeedRatings'], - Exif::JOB_TITLE => (!isset($source[self::SECTION_IPTC]['jobtitle'])) ? - false : $source[self::SECTION_IPTC]['jobtitle'], - Exif::KEYWORDS => (!isset($source[self::SECTION_IPTC]['keywords'])) ? - false : $source[self::SECTION_IPTC]['keywords'], - Exif::MIMETYPE => (!isset($source[Exif::MIMETYPE]) ? false : $source[Exif::MIMETYPE]), - Exif::ORIENTATION => (!isset($source[Exif::ORIENTATION]) ? false : $source[Exif::ORIENTATION]), - Exif::SOFTWARE => (!isset($source['Software'])) ? false : trim($source['Software']), - Exif::SOURCE => (!isset($source[self::SECTION_IPTC]['source'])) ? - false : $source[self::SECTION_IPTC]['source'], - Exif::TITLE => (!isset($source[self::SECTION_IPTC]['title'])) ? - false : $source[self::SECTION_IPTC]['title'], - Exif::VERTICAL_RESOLUTION => $vertResolution, - Exif::WIDTH => (!isset($source[self::SECTION_COMPUTED]['Width'])) ? - false : $source[self::SECTION_COMPUTED]['Width'], - Exif::GPS => $gpsLocation, - ); - - $arrMapping = array( - array( - Exif::AUTHOR => 'Artist', - Exif::CAMERA => 'Model', - Exif::EXPOSURE => 'ExposureTime', - Exif::ISO => 'ISOSpeedRatings', - Exif::SOFTWARE => 'Software', - ), - self::SECTION_COMPUTED => array( - Exif::APERTURE => 'ApertureFNumber', - Exif::FOCAL_DISTANCE => 'FocusDistance', - Exif::HEIGHT => 'Height', - Exif::WIDTH => 'Width', - ), - self::SECTION_IPTC => array( - Exif::CAPTION => 'caption', - Exif::COPYRIGHT => 'copyright', - Exif::CREDIT => 'credit', - Exif::HEADLINE => 'headline', - Exif::JOB_TITLE => 'jobtitle', - Exif::KEYWORDS => 'keywords', - Exif::SOURCE => 'source', - Exif::TITLE => 'title', - ), - ); - - foreach ($arrMapping as $key => $arrFields) { - if (array_key_exists($key, $source)) { - $arrSource = $source[$key]; - } else { - $arrSource = $source; - } - - foreach ($arrFields as $mappedField => $field) { - if (isset($arrSource[$field])) { - $mappedData[$mappedField] = $arrSource[$field]; - } - } - } - - return $mappedData; - } - - /** - * Extract GPS coordinates from components array - * - * @param array $components - * @return float - */ - protected function extractGPSCoordinate(array $components) - { - $components = array_map(array($this, 'normalizeGPSComponent'), $components); - - return intval($components[0]) + (intval($components[1]) / 60) + (floatval($components[2]) / 3600); - } - - /** - * Normalize GPS coordinates components - * - * @param mixed $component - * @return int|float - */ - protected function normalizeGPSComponent($component) - { - $parts = explode('/', $component); - - return count($parts) === 1 ? $parts[0] : (int) reset($parts) / (int) end($parts); - } } diff --git a/lib/PHPExif/Adapter/NoAdapterException.php b/lib/PHPExif/Adapter/NoAdapterException.php index 6d56104..8a2b011 100644 --- a/lib/PHPExif/Adapter/NoAdapterException.php +++ b/lib/PHPExif/Adapter/NoAdapterException.php @@ -7,6 +7,7 @@ * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License * @category PHPExif * @package Reader + * @codeCoverageIgnore */ namespace PHPExif\Adapter; diff --git a/lib/PHPExif/Exif.php b/lib/PHPExif/Exif.php index 4f39c6d..ba5080a 100755 --- a/lib/PHPExif/Exif.php +++ b/lib/PHPExif/Exif.php @@ -133,6 +133,19 @@ public function getAperture() return $this->data[self::APERTURE]; } + /** + * Sets the Aperture F-number + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setAperture($value) + { + $this->data[self::APERTURE] = $value; + + return $this; + } + /** * Returns the Author * @@ -147,6 +160,19 @@ public function getAuthor() return $this->data[self::AUTHOR]; } + /** + * Sets the Author + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setAuthor($value) + { + $this->data[self::AUTHOR] = $value; + + return $this; + } + /** * Returns the Headline * @@ -161,6 +187,19 @@ public function getHeadline() return $this->data[self::HEADLINE]; } + /** + * Sets the Headline + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setHeadline($value) + { + $this->data[self::HEADLINE] = $value; + + return $this; + } + /** * Returns the Credit * @@ -175,6 +214,19 @@ public function getCredit() return $this->data[self::CREDIT]; } + /** + * Sets the Credit + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setCredit($value) + { + $this->data[self::CREDIT] = $value; + + return $this; + } + /** * Returns the source * @@ -189,6 +241,19 @@ public function getSource() return $this->data[self::SOURCE]; } + /** + * Sets the Source + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setSource($value) + { + $this->data[self::SOURCE] = $value; + + return $this; + } + /** * Returns the Jobtitle * @@ -203,6 +268,19 @@ public function getJobtitle() return $this->data[self::JOB_TITLE]; } + /** + * Sets the Jobtitle + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setJobtitle($value) + { + $this->data[self::JOB_TITLE] = $value; + + return $this; + } + /** * Returns the ISO speed * @@ -217,6 +295,19 @@ public function getIso() return $this->data[self::ISO]; } + /** + * Sets the ISO + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setIso($value) + { + $this->data[self::ISO] = $value; + + return $this; + } + /** * Returns the Exposure * @@ -231,6 +322,19 @@ public function getExposure() return $this->data[self::EXPOSURE]; } + /** + * Sets the Exposure + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setExposure($value) + { + $this->data[self::EXPOSURE] = $value; + + return $this; + } + /** * Returns the Exposure * @@ -261,6 +365,19 @@ public function getFocusDistance() return $this->data[self::FOCAL_DISTANCE]; } + /** + * Sets the focus distance + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setFocusDistance($value) + { + $this->data[self::FOCAL_DISTANCE] = $value; + + return $this; + } + /** * Returns the width in pixels, if it exists * @@ -275,6 +392,19 @@ public function getWidth() return $this->data[self::WIDTH]; } + /** + * Sets the width + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setWidth($value) + { + $this->data[self::WIDTH] = $value; + + return $this; + } + /** * Returns the height in pixels, if it exists * @@ -289,6 +419,19 @@ public function getHeight() return $this->data[self::HEIGHT]; } + /** + * Sets the height + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setHeight($value) + { + $this->data[self::HEIGHT] = $value; + + return $this; + } + /** * Returns the title, if it exists * @@ -303,6 +446,19 @@ public function getTitle() return $this->data[self::TITLE]; } + /** + * Sets the title + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setTitle($value) + { + $this->data[self::TITLE] = $value; + + return $this; + } + /** * Returns the caption, if it exists * @@ -317,6 +473,19 @@ public function getCaption() return $this->data[self::CAPTION]; } + /** + * Sets the caption + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setCaption($value) + { + $this->data[self::CAPTION] = $value; + + return $this; + } + /** * Returns the copyright, if it exists * @@ -331,6 +500,19 @@ public function getCopyright() return $this->data[self::COPYRIGHT]; } + /** + * Sets the copyright + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setCopyright($value) + { + $this->data[self::COPYRIGHT] = $value; + + return $this; + } + /** * Returns the keywords, if they exists * @@ -345,6 +527,19 @@ public function getKeywords() return $this->data[self::KEYWORDS]; } + /** + * Sets the keywords + * + * @param array $value + * @return \PHPExif\Exif + */ + public function setKeywords($value) + { + $this->data[self::KEYWORDS] = $value; + + return $this; + } + /** * Returns the camera, if it exists * @@ -359,6 +554,19 @@ public function getCamera() return $this->data[self::CAMERA]; } + /** + * Sets the camera + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setCamera($value) + { + $this->data[self::CAMERA] = $value; + + return $this; + } + /** * Returns the horizontal resolution in DPI, if it exists * @@ -373,6 +581,19 @@ public function getHorizontalResolution() return $this->data[self::HORIZONTAL_RESOLUTION]; } + /** + * Sets the horizontal resolution in DPI + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setHorizontalResolution($value) + { + $this->data[self::HORIZONTAL_RESOLUTION] = $value; + + return $this; + } + /** * Returns the vertical resolution in DPI, if it exists * @@ -387,6 +608,19 @@ public function getVerticalResolution() return $this->data[self::VERTICAL_RESOLUTION]; } + /** + * Sets the vertical resolution in DPI + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setVerticalResolution($value) + { + $this->data[self::VERTICAL_RESOLUTION] = $value; + + return $this; + } + /** * Returns the software, if it exists * @@ -401,6 +635,19 @@ public function getSoftware() return $this->data[self::SOFTWARE]; } + /** + * Sets the software + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setSoftware($value) + { + $this->data[self::SOFTWARE] = trim($value); + + return $this; + } + /** * Returns the focal length in mm, if it exists * @@ -415,6 +662,19 @@ public function getFocalLength() return $this->data[self::FOCAL_LENGTH]; } + /** + * Sets the focal length in mm + * + * @param float $value + * @return \PHPExif\Exif + */ + public function setFocalLength($value) + { + $this->data[self::FOCAL_LENGTH] = $value; + + return $this; + } + /** * Returns the creation datetime, if it exists * @@ -429,52 +689,104 @@ public function getCreationDate() return $this->data[self::CREATION_DATE]; } + /** + * Sets the creation datetime + * + * @param \DateTime $value + * @return \PHPExif\Exif + */ + public function setCreationDate(\DateTime $value) + { + $this->data[self::CREATION_DATE] = $value; + + return $this; + } + /** * Returns the colorspace, if it exists * - * @return string + * @return string|boolean */ public function getColorSpace() { if (!isset($this->data[self::COLORSPACE])) { return false; } - + return $this->data[self::COLORSPACE]; } + /** + * Sets the colorspace + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setColorSpace($value) + { + $this->data[self::COLORSPACE] = $value; + + return $this; + } + /** * Returns the mimetype, if it exists * - * @return string + * @return string|boolean */ public function getMimeType() { if (!isset($this->data[self::MIMETYPE])) { return false; } - + return $this->data[self::MIMETYPE]; } /** - * Returns the filesize, if it exists + * Sets the mimetype * - * @return integer + * @param string $value + * @return \PHPExif\Exif + */ + public function setMimeType($value) + { + $this->data[self::MIMETYPE] = $value; + + return $this; + } + + /** + * Returns the filesize, if it exists + * + * @return int|boolean */ public function getFileSize() { if (!isset($this->data[self::FILESIZE])) { return false; } - + return $this->data[self::FILESIZE]; } + /** + * Sets the filesize + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setFileSize($value) + { + $this->data[self::FILESIZE] = $value; + + return $this; + } + /** * Returns the orientation, if it exists * - * @return integer + * @return int|boolean */ public function getOrientation() { @@ -485,6 +797,19 @@ public function getOrientation() return $this->data[self::ORIENTATION]; } + /** + * Sets the orientation + * + * @param int $value + * @return \PHPExif\Exif + */ + public function setOrientation($value) + { + $this->data[self::ORIENTATION] = $value; + + return $this; + } + /** * Returns GPS coordinates, if it exists * @@ -498,4 +823,17 @@ public function getGPS() return $this->data[self::GPS]; } + + /** + * Sets the GPS coordinates + * + * @param string $value + * @return \PHPExif\Exif + */ + public function setGPS($value) + { + $this->data[self::GPS] = $value; + + return $this; + } } diff --git a/lib/PHPExif/Hydrator/HydratorInterface.php b/lib/PHPExif/Hydrator/HydratorInterface.php new file mode 100644 index 0000000..d884327 --- /dev/null +++ b/lib/PHPExif/Hydrator/HydratorInterface.php @@ -0,0 +1,33 @@ + + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Hydrator + * @codeCoverageIgnore + */ + +namespace PHPExif\Hydrator; + +/** + * PHP Exif Hydrator + * + * Defines the interface for a hydrator + * + * @category PHPExif + * @package Hydrator + */ +interface HydratorInterface +{ + /** + * Hydrates given array of data into the given Exif object + * + * @param object $object + * @param array $data + * @return void + */ + public function hydrate($object, array $data); +} diff --git a/lib/PHPExif/Hydrator/Mutator.php b/lib/PHPExif/Hydrator/Mutator.php new file mode 100644 index 0000000..164d57c --- /dev/null +++ b/lib/PHPExif/Hydrator/Mutator.php @@ -0,0 +1,55 @@ + + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Hydrator + */ + +namespace PHPExif\Hydrator; + +/** + * PHP Exif Mutator Hydrator + * + * Hydrates an object by setting data with + * the class mutator methods + * + * @category PHPExif + * @package Hydrator + */ +class Mutator implements HydratorInterface +{ + /** + * Hydrates given array of data into the given Exif object + * + * @param object $object + * @param array $data + * @return void + */ + public function hydrate($object, array $data) + { + foreach ($data as $property => $value) { + $mutator = $this->determineMutator($property); + + if (method_exists($object, $mutator)) { + $object->$mutator($value); + } + } + } + + /** + * Determines the name of the mutator method for given property name + * + * @param string $property The property to determine the mutator for + * @return string The name of the mutator method + */ + protected function determineMutator($property) + { + $method = 'set' . ucfirst($property); + return $method; + } +} diff --git a/lib/PHPExif/Mapper/Exiftool.php b/lib/PHPExif/Mapper/Exiftool.php new file mode 100644 index 0000000..2be4c43 --- /dev/null +++ b/lib/PHPExif/Mapper/Exiftool.php @@ -0,0 +1,203 @@ + + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Mapper + */ + +namespace PHPExif\Mapper; + +use PHPExif\Exif; +use DateTime; + +/** + * PHP Exif Exiftool Mapper + * + * Maps Exiftool raw data to valid data for the \PHPExif\Exif class + * + * @category PHPExif + * @package Mapper + */ +class Exiftool implements MapperInterface +{ + const APERTURE = 'Aperture'; + const APPROXIMATEFOCUSDISTANCE = 'ApproximateFocusDistance'; + const ARTIST = 'Artist'; + const CAPTION = 'Caption'; + const CAPTIONABSTRACT = 'Caption-Abstract'; + const COLORSPACE = 'ColorSpace'; + const COPYRIGHT = 'Copyright'; + const CREATEDATE = 'CreateDate'; + const CREDIT = 'Credit'; + const EXPOSURETIME = 'ExposureTime'; + const FILESIZE = 'FileSize'; + const FOCALLENGTH = 'FocalLength'; + const HEADLINE = 'Headline'; + const IMAGEHEIGHT = 'ImageHeight'; + const IMAGEWIDTH = 'ImageWidth'; + const ISO = 'ISO'; + const JOBTITLE = 'JobTitle'; + const KEYWORDS = 'Keywords'; + const MIMETYPE = 'MIMEType'; + const MODEL = 'Model'; + const ORIENTATION = 'Orientation'; + const SOFTWARE = 'Software'; + const SOURCE = 'Source'; + const TITLE = 'Title'; + const XRESOLUTION = 'XResolution'; + const YRESOLUTION = 'YResolution'; + const GPSLATITUDE = 'GPSLatitude'; + const GPSLONGITUDE = 'GPSLongitude'; + + /** + * Maps the ExifTool fields to the fields of + * the \PHPExif\Exif class + * + * @var array + */ + protected $map = array( + self::APERTURE => Exif::APERTURE, + self::ARTIST => Exif::AUTHOR, + self::MODEL => Exif::CAMERA, + self::CAPTION => Exif::CAPTION, + self::COLORSPACE => Exif::COLORSPACE, + self::COPYRIGHT => Exif::COPYRIGHT, + self::CREATEDATE => Exif::CREATION_DATE, + self::CREDIT => Exif::CREDIT, + self::EXPOSURETIME => Exif::EXPOSURE, + self::FILESIZE => Exif::FILESIZE, + self::FOCALLENGTH => Exif::FOCAL_LENGTH, + self::APPROXIMATEFOCUSDISTANCE => Exif::FOCAL_DISTANCE, + self::HEADLINE => Exif::HEADLINE, + self::IMAGEHEIGHT => Exif::HEIGHT, + self::XRESOLUTION => Exif::HORIZONTAL_RESOLUTION, + self::ISO => Exif::ISO, + self::JOBTITLE => Exif::JOB_TITLE, + self::KEYWORDS => Exif::KEYWORDS, + self::MIMETYPE => Exif::MIMETYPE, + self::ORIENTATION => Exif::ORIENTATION, + self::SOFTWARE => Exif::SOFTWARE, + self::SOURCE => Exif::SOURCE, + self::TITLE => Exif::TITLE, + self::YRESOLUTION => Exif::VERTICAL_RESOLUTION, + self::IMAGEWIDTH => Exif::WIDTH, + self::CAPTIONABSTRACT => Exif::CAPTION, + self::GPSLATITUDE => Exif::GPS, + self::GPSLONGITUDE => Exif::GPS, + ); + + /** + * @var bool + */ + protected $numeric = true; + + /** + * Mutator method for the numeric property + * + * @param bool $numeric + * @return \PHPExif\Mapper\Exiftool + */ + public function setNumeric($numeric) + { + $this->numeric = (bool)$numeric; + + return $this; + } + + /** + * Maps the array of raw source data to the correct + * fields for the \PHPExif\Exif class + * + * @param array $data + * @return array + */ + public function mapRawData(array $data) + { + $mappedData = array(); + $gpsData = array(); + foreach ($data as $field => $value) { + if (!array_key_exists($field, $this->map)) { + // silently ignore unknown fields + continue; + } + + $key = $this->map[$field]; + + // manipulate the value if necessary + switch ($field) { + case self::APERTURE: + $value = sprintf('f/%01.1f', $value); + break; + case self::APPROXIMATEFOCUSDISTANCE: + $value = sprintf('%1$sm', $value); + break; + case self::CREATEDATE: + $value = DateTime::createFromFormat('Y:m:d H:i:s', $value); + break; + case self::EXPOSURETIME: + $value = '1/' . round(1 / $value); + break; + case self::FOCALLENGTH: + $focalLengthParts = explode(' ', $value); + $value = (int) reset($focalLengthParts); + break; + case self::GPSLATITUDE: + $gpsData['lat'] = $this->extractGPSCoordinates($value); + break; + case self::GPSLONGITUDE: + $gpsData['lon'] = $this->extractGPSCoordinates($value); + break; + } + + // set end result + $mappedData[$key] = $value; + } + + // add GPS coordinates, if available + if (count($gpsData) === 2) { + $latitude = $gpsData['lat']; + $longitude = $gpsData['lon']; + + if ($latitude !== false && $longitude !== false) { + $gpsLocation = sprintf( + '%s,%s', + (strtoupper($data['GPSLatitudeRef'][0]) === 'S' ? -1 : 1) * $latitude, + (strtoupper($data['GPSLongitudeRef'][0]) === 'W' ? -1 : 1) * $longitude + ); + + $key = $this->map[self::GPSLATITUDE]; + + $mappedData[$key] = $gpsLocation; + } else { + unset($mappedData[$this->map[self::GPSLATITUDE]]); + } + } else { + unset($mappedData[$this->map[self::GPSLATITUDE]]); + } + + return $mappedData; + } + + /** + * Extract GPS coordinates from formatted string + * + * @param string $coordinates + * @return array + */ + protected function extractGPSCoordinates($coordinates) + { + if ($this->numeric === true) { + return abs((float) $coordinates); + } else { + if (!preg_match('!^([0-9.]+) deg ([0-9.]+)\' ([0-9.]+)"!', $coordinates, $matches)) { + return false; + } + + return intval($matches[1]) + (intval($matches[2]) / 60) + (floatval($matches[3]) / 3600); + } + } +} diff --git a/lib/PHPExif/Mapper/MapperInterface.php b/lib/PHPExif/Mapper/MapperInterface.php new file mode 100644 index 0000000..5f5097e --- /dev/null +++ b/lib/PHPExif/Mapper/MapperInterface.php @@ -0,0 +1,33 @@ + + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Mapper + * @codeCoverageIgnore + */ + +namespace PHPExif\Mapper; + +/** + * PHP Exif Mapper + * + * Defines the interface for data mappers + * + * @category PHPExif + * @package Mapper + */ +interface MapperInterface +{ + /** + * Maps the array of raw source data to the correct + * fields for the \PHPExif\Exif class + * + * @param array $data + * @return array + */ + public function mapRawData(array $data); +} diff --git a/lib/PHPExif/Mapper/Native.php b/lib/PHPExif/Mapper/Native.php new file mode 100644 index 0000000..ab68853 --- /dev/null +++ b/lib/PHPExif/Mapper/Native.php @@ -0,0 +1,225 @@ + + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Mapper + */ + +namespace PHPExif\Mapper; + +use PHPExif\Exif; +use DateTime; + +/** + * PHP Exif Native Mapper + * + * Maps native raw data to valid data for the \PHPExif\Exif class + * + * @category PHPExif + * @package Mapper + */ +class Native implements MapperInterface +{ + const APERTUREFNUMBER = 'ApertureFNumber'; + const ARTIST = 'Artist'; + const CAPTION = 'caption'; + const COLORSPACE = 'ColorSpace'; + const COPYRIGHT = 'copyright'; + const DATETIMEORIGINAL = 'DateTimeOriginal'; + const CREDIT = 'credit'; + const EXPOSURETIME = 'ExposureTime'; + const FILESIZE = 'FileSize'; + const FOCALLENGTH = 'FocalLength'; + const FOCUSDISTANCE = 'FocusDistance'; + const HEADLINE = 'headline'; + const HEIGHT = 'Height'; + const ISOSPEEDRATINGS = 'ISOSpeedRatings'; + const JOBTITLE = 'jobtitle'; + const KEYWORDS = 'keywords'; + const MIMETYPE = 'MimeType'; + const MODEL = 'Model'; + const ORIENTATION = 'Orientation'; + const SOFTWARE = 'Software'; + const SOURCE = 'source'; + const TITLE = 'title'; + const WIDTH = 'Width'; + const XRESOLUTION = 'XResolution'; + const YRESOLUTION = 'YResolution'; + const GPSLATITUDE = 'GPSLatitude'; + const GPSLONGITUDE = 'GPSLongitude'; + + const SECTION_FILE = 'FILE'; + const SECTION_COMPUTED = 'COMPUTED'; + const SECTION_IFD0 = 'IFD0'; + const SECTION_THUMBNAIL = 'THUMBNAIL'; + const SECTION_COMMENT = 'COMMENT'; + const SECTION_EXIF = 'EXIF'; + const SECTION_ALL = 'ANY_TAG'; + const SECTION_IPTC = 'IPTC'; + + /** + * A list of section names + * + * @var array + */ + protected $sections = array( + self::SECTION_FILE, + self::SECTION_COMPUTED, + self::SECTION_IFD0, + self::SECTION_THUMBNAIL, + self::SECTION_COMMENT, + self::SECTION_EXIF, + self::SECTION_ALL, + self::SECTION_IPTC, + ); + + /** + * Maps the ExifTool fields to the fields of + * the \PHPExif\Exif class + * + * @var array + */ + protected $map = array( + self::APERTUREFNUMBER => Exif::APERTURE, + self::FOCUSDISTANCE => Exif::FOCAL_DISTANCE, + self::HEIGHT => Exif::HEIGHT, + self::WIDTH => Exif::WIDTH, + self::CAPTION => Exif::CAPTION, + self::COPYRIGHT => Exif::COPYRIGHT, + self::CREDIT => Exif::CREDIT, + self::HEADLINE => Exif::HEADLINE, + self::JOBTITLE => Exif::JOB_TITLE, + self::KEYWORDS => Exif::KEYWORDS, + self::SOURCE => Exif::SOURCE, + self::TITLE => Exif::TITLE, + self::ARTIST => Exif::AUTHOR, + self::MODEL => Exif::CAMERA, + self::COLORSPACE => Exif::COLORSPACE, + self::DATETIMEORIGINAL => Exif::CREATION_DATE, + self::EXPOSURETIME => Exif::EXPOSURE, + self::FILESIZE => Exif::FILESIZE, + self::FOCALLENGTH => Exif::FOCAL_LENGTH, + self::ISOSPEEDRATINGS => Exif::ISO, + self::MIMETYPE => Exif::MIMETYPE, + self::ORIENTATION => Exif::ORIENTATION, + self::SOFTWARE => Exif::SOFTWARE, + self::XRESOLUTION => Exif::HORIZONTAL_RESOLUTION, + self::YRESOLUTION => Exif::VERTICAL_RESOLUTION, + self::GPSLATITUDE => Exif::GPS, + self::GPSLONGITUDE => Exif::GPS, + ); + + /** + * Maps the array of raw source data to the correct + * fields for the \PHPExif\Exif class + * + * @param array $data + * @return array + */ + public function mapRawData(array $data) + { + $mappedData = array(); + $gpsData = array(); + foreach ($data as $field => $value) { + if ($this->isSection($field) && is_array($value)) { + $subData = $this->mapRawData($value); + + $mappedData = array_merge($mappedData, $subData); + continue; + } + + if (!array_key_exists($field, $this->map)) { + // silently ignore unknown fields + continue; + } + + $key = $this->map[$field]; + + // manipulate the value if necessary + switch ($field) { + case self::DATETIMEORIGINAL: + $value = DateTime::createFromFormat('Y:m:d H:i:s', $value); + break; + case self::EXPOSURETIME: + // normalize ExposureTime + // on one test image, it reported "10/300" instead of "1/30" + list($counter, $denominator) = explode('/', $value); + if (intval($counter) !== 1) { + $denominator /= $counter; + } + $value = '1/' . round($denominator); + break; + case self::FOCALLENGTH: + $parts = explode('/', $value); + $value = (int)reset($parts) / (int)end($parts); + break; + case self::XRESOLUTION: + case self::YRESOLUTION: + $resolutionParts = explode('/', $value); + $value = (int)reset($resolutionParts); + break; + case self::GPSLATITUDE: + $gpsData['lat'] = $this->extractGPSCoordinate($value); + break; + case self::GPSLONGITUDE: + $gpsData['lon'] = $this->extractGPSCoordinate($value); + break; + } + + // set end result + $mappedData[$key] = $value; + } + + if (count($gpsData) === 2) { + $gpsLocation = sprintf( + '%s,%s', + (strtoupper($data['GPSLatitudeRef'][0]) === 'S' ? -1 : 1) * $gpsData['lat'], + (strtoupper($data['GPSLongitudeRef'][0]) === 'W' ? -1 : 1) * $gpsData['lon'] + ); + $mappedData[Exif::GPS] = $gpsLocation; + } + + return $mappedData; + } + + /** + * Determines if given field is a section + * + * @param string $field + * @return bool + */ + protected function isSection($field) + { + return (in_array($field, $this->sections)); + } + + /** + * Extract GPS coordinates from components array + * + * @param array $components + * @return float + */ + protected function extractGPSCoordinate(array $components) + { + $components = array_map(array($this, 'normalizeGPSComponent'), $components); + + return intval($components[0]) + (intval($components[1]) / 60) + (floatval($components[2]) / 3600); + } + + /** + * Normalize GPS coordinates components + * + * @param mixed $component + * @return int|float + */ + protected function normalizeGPSComponent($component) + { + $parts = explode('/', $component); + + return count($parts) === 1 ? $parts[0] : (int) reset($parts) / (int) end($parts); + } +} diff --git a/lib/PHPExif/Reader/Reader.php b/lib/PHPExif/Reader/Reader.php index 241193e..34b7bb5 100755 --- a/lib/PHPExif/Reader/Reader.php +++ b/lib/PHPExif/Reader/Reader.php @@ -72,7 +72,6 @@ public function getAdapter() public static function factory($type) { $classname = get_called_class(); - $adapter = null; switch ($type) { case self::TYPE_NATIVE: $adapter = new NativeAdapter(); @@ -84,7 +83,6 @@ public static function factory($type) throw new \InvalidArgumentException( sprintf('Unknown type "%1$s"', $type) ); - break; } return new $classname($adapter); } diff --git a/lib/PHPExif/Reader/ReaderInterface.php b/lib/PHPExif/Reader/ReaderInterface.php index 76423e2..9617396 100644 --- a/lib/PHPExif/Reader/ReaderInterface.php +++ b/lib/PHPExif/Reader/ReaderInterface.php @@ -1,7 +1,26 @@ + * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License + * @category PHPExif + * @package Reader + * @codeCoverageIgnore + */ namespace PHPExif\Reader; +/** + * PHP Exif Reader + * + * Defines the interface for reader functionality + * + * @category PHPExif + * @package Reader + */ interface ReaderInterface { /** diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7a43d54..7470936 100755 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,6 +14,7 @@ ./lib ./autoload.php + *Interface.php diff --git a/tests/PHPExif/Adapter/AdapterAbstractTest.php b/tests/PHPExif/Adapter/AdapterAbstractTest.php index 97d2794..46982da 100644 --- a/tests/PHPExif/Adapter/AdapterAbstractTest.php +++ b/tests/PHPExif/Adapter/AdapterAbstractTest.php @@ -1,5 +1,8 @@ + * @covers \PHPExif\Adapter\AdapterInterface + */ class AdapterAbstractTest extends \PHPUnit_Framework_TestCase { /** @@ -14,161 +17,216 @@ public function setUp() /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::determinePropertyGetter + * @covers \PHPExif\Adapter\AdapterAbstract::setOptions */ - public function testDeterminePropertyGetter() + public function testSetOptionsReturnsCurrentInstance() { - $reflMethod = new \ReflectionMethod('\PHPExif\Adapter\Native', 'determinePropertyGetter'); - $reflMethod->setAccessible(true); + $result = $this->adapter->setOptions(array()); + $this->assertSame($this->adapter, $result); + } - $result = $reflMethod->invoke( - $this->adapter, - 'foo' + /** + * @group adapter + * @covers \PHPExif\Adapter\AdapterAbstract::setOptions + */ + public function testSetOptionsCorrectlySetsProperties() + { + $expected = array( + 'requiredSections' => array('foo', 'bar', 'baz',), + 'includeThumbnail' => \PHPExif\Adapter\Native::INCLUDE_THUMBNAIL, + 'sectionsAsArrays' => \PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, ); + $this->adapter->setOptions($expected); - $this->assertEquals('getFoo', $result); + foreach ($expected as $key => $value) { + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\Native', $key); + $reflProp->setAccessible(true); + $this->assertEquals($value, $reflProp->getValue($this->adapter)); + } } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::determinePropertySetter + * @covers \PHPExif\Adapter\AdapterAbstract::setOptions */ - public function testDeterminePropertySetter() + public function testSetOptionsIgnoresPropertiesWithoutSetters() { - $reflMethod = new \ReflectionMethod('\PHPExif\Adapter\Native', 'determinePropertySetter'); - $reflMethod->setAccessible(true); + $expected = array( + 'iptcMapping' => array('foo', 'bar', 'baz'), + ); + $this->adapter->setOptions($expected); + + foreach ($expected as $key => $value) { + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\Native', $key); + $reflProp->setAccessible(true); + $this->assertNotEquals($value, $reflProp->getValue($this->adapter)); + } + } + - $result = $reflMethod->invoke( - $this->adapter, - 'foo' + /** + * @group adapter + * @covers \PHPExif\Adapter\AdapterAbstract::__construct + */ + public function testConstructorSetsOptions() + { + $expected = array( + 'requiredSections' => array('foo', 'bar', 'baz',), + 'includeThumbnail' => \PHPExif\Adapter\Native::INCLUDE_THUMBNAIL, + 'sectionsAsArrays' => \PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, ); + $adapter = new \PHPExif\Adapter\Native($expected); - $this->assertEquals('setFoo', $result); + foreach ($expected as $key => $value) { + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\Native', $key); + $reflProp->setAccessible(true); + $this->assertEquals($value, $reflProp->getValue($adapter)); + } } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::getClassConstantsOfType + * @covers \PHPExif\Adapter\AdapterAbstract::setMapper */ - public function testGetClassConstantsOfTypeAlwaysReturnsArray() + public function testSetMapperReturnsCurrentInstance() { - $result = $this->adapter->getClassConstantsOfType('sections'); - $this->assertInternalType('array', $result); - $result = $this->adapter->getClassConstantsOfType('foo'); - $this->assertInternalType('array', $result); + $mapper = new \PHPExif\Mapper\Native(); + $result = $this->adapter->setMapper($mapper); + $this->assertSame($this->adapter, $result); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::getClassConstantsOfType + * @covers \PHPExif\Adapter\AdapterAbstract::setMapper */ - public function testGetClassConstantsOfTypeReturnsCorrectData() + public function testSetMapperCorrectlySetsInProperty() { - $expected = array( - 'SECTIONS_AS_ARRAYS' => \PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, - 'SECTIONS_FLAT' => \PHPExif\Adapter\Native::SECTIONS_FLAT, - ); - $actual = $this->adapter->getClassConstantsOfType('sections'); - $this->assertEquals($expected, $actual); + $mapper = new \PHPExif\Mapper\Native(); + $this->adapter->setMapper($mapper); + + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\AdapterAbstract', 'mapper'); + $reflProp->setAccessible(true); + $this->assertSame($mapper, $reflProp->getValue($this->adapter)); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::toArray + * @covers \PHPExif\Adapter\AdapterAbstract::getMapper */ - public function testToArrayReturnsPropertiesWithGetters() + public function testGetMapperCorrectlyReturnsFromProperty() { - $expected = array( - 'requiredSections', - 'includeThumbnail', - 'sectionsAsArrays', + $mapper = new \PHPExif\Mapper\Native(); + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\AdapterAbstract', 'mapper'); + $reflProp->setAccessible(true); + $reflProp->setValue($this->adapter, $mapper); + $this->assertSame($mapper, $this->adapter->getMapper()); + } + + /** + * @group adapter + * @covers \PHPExif\Adapter\AdapterAbstract::getMapper + */ + public function testGetMapperLazyLoadsMapperWhenNotPresent() + { + $reflProp = new \ReflectionProperty( + get_class($this->adapter), + 'mapperClass' ); - $result = $this->adapter->toArray(); - $actual = array_keys($result); - $this->assertEquals($expected, $actual); + + $mapperClass = '\\PHPExif\\Mapper\\Native'; + $reflProp->setAccessible(true); + $reflProp->setValue($this->adapter, $mapperClass); + + $this->assertInstanceOf($mapperClass, $this->adapter->getMapper()); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::toArray + * @covers \PHPExif\Adapter\AdapterAbstract::getMapper */ - public function testToArrayOmmitsPropertiesWithoutGetters() + public function testGetMapperLazyLoadingSetsInProperty() { - $expected = array( - 'iptcMapping', + $reflProp = new \ReflectionProperty( + get_class($this->adapter), + 'mapperClass' ); - $result = $this->adapter->toArray(); - $actual = array_keys($result); - $diff = array_diff($expected, $actual); - $this->assertEquals($expected, $diff); + + $mapperClass = '\\PHPExif\\Mapper\\Native'; + $reflProp->setAccessible(true); + $reflProp->setValue($this->adapter, $mapperClass); + + $reflProp2 = new \ReflectionProperty( + get_class($this->adapter), + 'mapper' + ); + $reflProp2->setAccessible(true); + $this->adapter->getMapper(); + $this->assertInstanceOf($mapperClass, $reflProp2->getValue($this->adapter)); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::setOptions + * @covers \PHPExif\Adapter\AdapterAbstract::setHydrator */ - public function testSetOptionsReturnsCurrentInstance() + public function testSetHydratorReturnsCurrentInstance() { - $result = $this->adapter->setOptions(array()); + $hydrator = new \PHPExif\Hydrator\Mutator(); + $result = $this->adapter->setHydrator($hydrator); $this->assertSame($this->adapter, $result); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::setOptions + * @covers \PHPExif\Adapter\AdapterAbstract::setHydrator */ - public function testSetOptionsCorrectlySetsProperties() + public function testSetHydratorCorrectlySetsInProperty() { - $expected = array( - 'requiredSections' => array('foo', 'bar', 'baz',), - 'includeThumbnail' => \PHPExif\Adapter\Native::INCLUDE_THUMBNAIL, - 'sectionsAsArrays' => \PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, - ); - $this->adapter->setOptions($expected); + $hydrator = new \PHPExif\Hydrator\Mutator(); + $this->adapter->setHydrator($hydrator); - foreach ($expected as $key => $value) { - $reflProp = new \ReflectionProperty('\PHPExif\Adapter\Native', $key); - $reflProp->setAccessible(true); - $this->assertEquals($value, $reflProp->getValue($this->adapter)); - } + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\AdapterAbstract', 'hydrator'); + $reflProp->setAccessible(true); + $this->assertSame($hydrator, $reflProp->getValue($this->adapter)); } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::setOptions + * @covers \PHPExif\Adapter\AdapterAbstract::getHydrator */ - public function testSetOptionsIgnoresPropertiesWithoutSetters() + public function testGetHydratorCorrectlyReturnsFromProperty() { - $expected = array( - 'iptcMapping' => array('foo', 'bar', 'baz'), - ); - $this->adapter->setOptions($expected); - - foreach ($expected as $key => $value) { - $reflProp = new \ReflectionProperty('\PHPExif\Adapter\Native', $key); - $reflProp->setAccessible(true); - $this->assertNotEquals($value, $reflProp->getValue($this->adapter)); - } + $hydrator = new \PHPExif\Hydrator\Mutator(); + $reflProp = new \ReflectionProperty('\\PHPExif\\Adapter\\AdapterAbstract', 'hydrator'); + $reflProp->setAccessible(true); + $reflProp->setValue($this->adapter, $hydrator); + $this->assertSame($hydrator, $this->adapter->getHydrator()); } + /** + * @group adapter + * @covers \PHPExif\Adapter\AdapterAbstract::getHydrator + */ + public function testGetHydratorLazyLoadsHydratorWhenNotPresent() + { + $hydratorClass = '\\PHPExif\\Hydrator\\Mutator'; + $this->assertInstanceOf($hydratorClass, $this->adapter->getHydrator()); + } /** * @group adapter - * @covers \PHPExif\Adapter\AdapterAbstract::__construct + * @covers \PHPExif\Adapter\AdapterAbstract::getHydrator */ - public function testConstructorSetsOptions() + public function testGetHydratorLazyLoadingSetsInProperty() { - $expected = array( - 'requiredSections' => array('foo', 'bar', 'baz',), - 'includeThumbnail' => \PHPExif\Adapter\Native::INCLUDE_THUMBNAIL, - 'sectionsAsArrays' => \PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, - ); - $adapter = new \PHPExif\Adapter\Native($expected); + $hydratorClass = '\\PHPExif\\Hydrator\\Mutator'; - foreach ($expected as $key => $value) { - $reflProp = new \ReflectionProperty('\PHPExif\Adapter\Native', $key); - $reflProp->setAccessible(true); - $this->assertEquals($value, $reflProp->getValue($adapter)); - } + $reflProp = new \ReflectionProperty( + get_class($this->adapter), + 'hydrator' + ); + $reflProp->setAccessible(true); + $this->adapter->getHydrator(); + $this->assertInstanceOf($hydratorClass, $reflProp->getValue($this->adapter)); } } + diff --git a/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php b/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php new file mode 100644 index 0000000..9d9bd39 --- /dev/null +++ b/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php @@ -0,0 +1,68 @@ + + */ + class ExiftoolProcOpenTest extends \PHPUnit_Framework_TestCase + { + /** + * @var \PHPExif\Adapter\Exiftool + */ + protected $adapter; + + public function setUp() + { + global $mockProcOpen; + $mockProcOpen = true; + $this->adapter = new \PHPExif\Adapter\Exiftool(); + } + + public function tearDown() + { + global $mockProcOpen; + $mockProcOpen = false; + } + + /** + * @group exiftool + * @covers \PHPExif\Adapter\Exiftool::getCliOutput + * @expectedException RuntimeException + */ + public function testGetCliOutput() + { + $reflMethod = new \ReflectionMethod('\PHPExif\Adapter\Exiftool', 'getCliOutput'); + $reflMethod->setAccessible(true); + + $result = $reflMethod->invoke( + $this->adapter, + sprintf( + '%1$s', + 'pwd' + ) + ); + } + } +} diff --git a/tests/PHPExif/Adapter/ExiftoolTest.php b/tests/PHPExif/Adapter/ExiftoolTest.php index 5e8783b..71abd2a 100644 --- a/tests/PHPExif/Adapter/ExiftoolTest.php +++ b/tests/PHPExif/Adapter/ExiftoolTest.php @@ -1,4 +1,7 @@ + */ class ExiftoolTest extends \PHPUnit_Framework_TestCase { /** @@ -60,114 +63,32 @@ public function testGetToolPathLazyLoadsPath() $this->assertInternalType('string', $this->adapter->getToolPath()); } - /** - * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::getExifFromFile - */ - public function testGetExifFromFile() - { - $file = PHPEXIF_TEST_ROOT . '/files/morning_glory_pool_500.jpg'; - $result = $this->adapter->getExifFromFile($file); - $this->assertInstanceOf('\PHPExif\Exif', $result); - $this->assertInternalType('array', $result->getRawData()); - $this->assertNotEmpty($result->getRawData()); - } - - /** - * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::mapData - */ - public function testMapDataReturnsArray() - { - $this->assertInternalType('array', $this->adapter->mapData(array())); - } - - /** - * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::mapData - */ - public function testMapDataReturnsArrayFalseValuesIfUndefined() - { - $result = $this->adapter->mapData(array()); - - foreach ($result as $value) { - $this->assertFalse($value); - } - } - - /** - * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::mapData - */ - public function testMapDataResultHasAllKeys() - { - $reflClass = new \ReflectionClass('\PHPExif\Exif'); - $constants = $reflClass->getConstants(); - $result = $this->adapter->mapData(array()); - $keys = array_keys($result); - - $diff = array_diff($constants, $keys); - - $this->assertEquals(0, count($diff)); - } - - /** - * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::mapData - */ - public function testMapDataFocalLengthIsCalculated() - { - $focalLength = '18 mm.'; - - $result = $this->adapter->mapData( - array( - 'FocalLength' => $focalLength, - ) - ); - - $this->assertEquals(18, $result[\PHPExif\Exif::FOCAL_LENGTH]); - } - /** * @group exiftool * @covers \PHPExif\Adapter\Exiftool::setNumeric - * @covers \PHPExif\Adapter\Exiftool::mapData - * @covers \PHPExif\Adapter\Exiftool::extractGPSCoordinates */ - public function testMapDataCreationDegGPSIsCalculated() + public function testSetNumericInProperty() { - $this->adapter->setNumeric(false); - $result = $this->adapter->mapData( - array( - 'GPSLatitude' => '40 deg 20\' 0.42857" N', - 'GPSLatitudeRef' => 'North', - 'GPSLongitude' => '20 deg 10\' 2.33333" W', - 'GPSLongitudeRef' => 'West', - ) - ); + $reflProperty = new \ReflectionProperty('\PHPExif\Adapter\Exiftool', 'numeric'); + $reflProperty->setAccessible(true); - $expected = '40.333452380556,-20.167314813889'; - $this->assertEquals($expected, $result[\PHPExif\Exif::GPS]); + $expected = true; + $this->adapter->setNumeric($expected); + + $this->assertEquals($expected, $reflProperty->getValue($this->adapter)); } /** * @group exiftool - * @covers \PHPExif\Adapter\Exiftool::mapData - * @covers \PHPExif\Adapter\Exiftool::extractGPSCoordinates + * @covers \PHPExif\Adapter\Exiftool::getExifFromFile */ - public function testMapDataCreationNumericGPSIsCalculated() + public function testGetExifFromFile() { - $result = $this->adapter->mapData( - array( - 'GPSLatitude' => '40.333452381', - 'GPSLatitudeRef' => 'North', - 'GPSLongitude' => '20.167314814', - 'GPSLongitudeRef' => 'West', - ) - ); - - $expected = '40.333452381,-20.167314814'; - $this->assertEquals($expected, $result[\PHPExif\Exif::GPS]); + $file = PHPEXIF_TEST_ROOT . '/files/morning_glory_pool_500.jpg'; + $result = $this->adapter->getExifFromFile($file); + $this->assertInstanceOf('\PHPExif\Exif', $result); + $this->assertInternalType('array', $result->getRawData()); + $this->assertNotEmpty($result->getRawData()); } /** diff --git a/tests/PHPExif/Adapter/NativeTest.php b/tests/PHPExif/Adapter/NativeTest.php index 381f506..7dd86b6 100755 --- a/tests/PHPExif/Adapter/NativeTest.php +++ b/tests/PHPExif/Adapter/NativeTest.php @@ -1,4 +1,7 @@ + */ class NativeTest extends \PHPUnit_Framework_TestCase { /** @@ -179,165 +182,4 @@ public function testGetSectionsAsArrayFromProperty() $this->assertEquals(\PHPExif\Adapter\Native::SECTIONS_AS_ARRAYS, $this->adapter->getSectionsAsArrays()); } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataReturnsArray() - { - $this->assertInternalType('array', $this->adapter->mapData(array())); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataMapsFirstLevel() - { - $result = $this->adapter->mapData( - array( - 'Software' => 'Foo', - ) - ); - $this->assertEquals( - 'Foo', - $result[\PHPExif\Exif::SOFTWARE] - ); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataMapsSecondLevel() - { - $result = $this->adapter->mapData( - array( - \PHPExif\Adapter\Native::SECTION_COMPUTED => array( - 'Height' => '1500' - ) - ) - ); - $this->assertEquals( - 1500, - $result[\PHPExif\Exif::HEIGHT] - ); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataReturnsArrayFalseValuesIfUndefined() - { - $result = $this->adapter->mapData(array()); - - foreach ($result as $key => $value) { - $this->assertFalse($value); - } - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataResultHasAllKeys() - { - $reflClass = new \ReflectionClass('\PHPExif\Exif'); - $constants = $reflClass->getConstants(); - $result = $this->adapter->mapData(array()); - $keys = array_keys($result); - - $diff = array_diff($constants, $keys); - - $this->assertEquals(0, count($diff)); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataFocalLengthIsCalculated() - { - $focalLength = '1/320'; - - $result = $this->adapter->mapData( - array( - 'FocalLength' => $focalLength, - ) - ); - - $this->assertEquals(1/320, $result[\PHPExif\Exif::FOCAL_LENGTH]); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataHorizontalResolutionIsCalculated() - { - $xRes = '240/1'; - - $result = $this->adapter->mapData( - array( - 'XResolution' => $xRes, - ) - ); - - $this->assertEquals(240, $result[\PHPExif\Exif::HORIZONTAL_RESOLUTION]); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataVerticalResolutionIsCalculated() - { - $yRes = '240/1'; - - $result = $this->adapter->mapData( - array( - 'YResolution' => $yRes, - ) - ); - - $this->assertEquals(240, $result[\PHPExif\Exif::VERTICAL_RESOLUTION]); - } - - /** - * @group native-curr - * @covers \PHPExif\Adapter\Native::mapData - */ - public function testMapDataCreationDateIsConvertedToDatetime() - { - $result = $this->adapter->mapData( - array( - 'DateTimeOriginal' => '2013:06:30 12:34:56', - ) - ); - - $this->assertInstanceOf('DateTime', $result[\PHPExif\Exif::CREATION_DATE]); - } - - /** - * @group native - * @covers \PHPExif\Adapter\Native::mapData - * @covers \PHPExif\Adapter\Native::extractGPSCoordinate - * @covers \PHPExif\Adapter\Native::normalizeGPSComponent - */ - public function testMapDataCreationGPSIsCalculated() - { - $result = $this->adapter->mapData( - array( - 'GPSLatitude' => array('40/1', '20/1', '15/35'), - 'GPSLatitudeRef' => 'N', - 'GPSLongitude' => array('20/1', '10/1', '35/15'), - 'GPSLongitudeRef' => 'W', - ) - ); - - $expected = '40.333452380952,-20.167314814815'; - $this->assertEquals($expected, $result[\PHPExif\Exif::GPS]); - } } diff --git a/tests/PHPExif/ExifTest.php b/tests/PHPExif/ExifTest.php index 8f51f21..964a917 100755 --- a/tests/PHPExif/ExifTest.php +++ b/tests/PHPExif/ExifTest.php @@ -1,5 +1,7 @@ + */ class ExifTest extends \PHPUnit_Framework_TestCase { /** @@ -17,6 +19,7 @@ public function setUp() /** * @group exif + * @covers \PHPExif\Exif::__construct */ public function testConstructorCallsSetData() { @@ -42,6 +45,7 @@ public function testConstructorCallsSetData() /** * @group exif + * @covers \PHPExif\Exif::getRawData */ public function testGetRawData() { @@ -53,6 +57,7 @@ public function testGetRawData() /** * @group exif + * @covers \PHPExif\Exif::setRawData */ public function testSetRawData() { @@ -68,6 +73,7 @@ public function testSetRawData() /** * @group exif + * @covers \PHPExif\Exif::getData */ public function testGetData() { @@ -79,6 +85,7 @@ public function testGetData() /** * @group exif + * @covers \PHPExif\Exif::setData */ public function testSetData() { @@ -95,6 +102,33 @@ public function testSetData() /** * * @dataProvider providerUndefinedPropertiesReturnFalse + * @covers \PHPExif\Exif::getAperture + * @covers \PHPExif\Exif::getIso + * @covers \PHPExif\Exif::getExposure + * @covers \PHPExif\Exif::getExposureMilliseconds + * @covers \PHPExif\Exif::getFocusDistance + * @covers \PHPExif\Exif::getWidth + * @covers \PHPExif\Exif::getHeight + * @covers \PHPExif\Exif::getTitle + * @covers \PHPExif\Exif::getCaption + * @covers \PHPExif\Exif::getCopyright + * @covers \PHPExif\Exif::getKeywords + * @covers \PHPExif\Exif::getCamera + * @covers \PHPExif\Exif::getHorizontalResolution + * @covers \PHPExif\Exif::getVerticalResolution + * @covers \PHPExif\Exif::getSoftware + * @covers \PHPExif\Exif::getFocalLength + * @covers \PHPExif\Exif::getCreationDate + * @covers \PHPExif\Exif::getAuthor + * @covers \PHPExif\Exif::getCredit + * @covers \PHPExif\Exif::getSource + * @covers \PHPExif\Exif::getJobtitle + * @covers \PHPExif\Exif::getMimeType + * @covers \PHPExif\Exif::getFileSize + * @covers \PHPExif\Exif::getHeadline + * @covers \PHPExif\Exif::getColorSpace + * @covers \PHPExif\Exif::getOrientation + * @covers \PHPExif\Exif::getGPS * @param string $accessor */ public function testUndefinedPropertiesReturnFalse($accessor) @@ -133,6 +167,12 @@ public function providerUndefinedPropertiesReturnFalse() array('getCredit'), array('getSource'), array('getJobtitle'), + array('getMimeType'), + array('getFileSize'), + array('getHeadline'), + array('getColorSpace'), + array('getOrientation'), + array('getGPS'), ); } @@ -438,6 +478,10 @@ public function testGetFileSize() $this->assertEquals($expected, $this->exif->getFileSize()); } + /** + * @group exif + * @covers \PHPExif\Exif::getOrientation + */ public function testGetOrientation() { $expected = 1; @@ -458,8 +502,99 @@ public function testGetGPS() $this->assertEquals($expected, $this->exif->getGPS()); } + /** + * @group exif + * @covers \PHPExif\Exif::setAperture + * @covers \PHPExif\Exif::setIso + * @covers \PHPExif\Exif::setExposure + * @covers \PHPExif\Exif::setFocusDistance + * @covers \PHPExif\Exif::setWidth + * @covers \PHPExif\Exif::setHeight + * @covers \PHPExif\Exif::setTitle + * @covers \PHPExif\Exif::setCaption + * @covers \PHPExif\Exif::setCopyright + * @covers \PHPExif\Exif::setKeywords + * @covers \PHPExif\Exif::setCamera + * @covers \PHPExif\Exif::setHorizontalResolution + * @covers \PHPExif\Exif::setVerticalResolution + * @covers \PHPExif\Exif::setSoftware + * @covers \PHPExif\Exif::setFocalLength + * @covers \PHPExif\Exif::setCreationDate + * @covers \PHPExif\Exif::setAuthor + * @covers \PHPExif\Exif::setCredit + * @covers \PHPExif\Exif::setSource + * @covers \PHPExif\Exif::setJobtitle + * @covers \PHPExif\Exif::setMimeType + * @covers \PHPExif\Exif::setFileSize + * @covers \PHPExif\Exif::setHeadline + * @covers \PHPExif\Exif::setColorSpace + * @covers \PHPExif\Exif::setOrientation + * @covers \PHPExif\Exif::setGPS + */ + public function testMutatorMethodsSetInProperty() + { + $reflClass = new \ReflectionClass(get_class($this->exif)); + $constants = $reflClass->getConstants(); + + $reflProp = new \ReflectionProperty(get_class($this->exif), 'data'); + $reflProp->setAccessible(true); + + $expected = 'foo'; + foreach ($constants as $name => $value) { + $setter = 'set' . ucfirst($value); + + switch ($value) { + case 'creationdate': + $now = new \DateTime(); + $this->exif->$setter($now); + $propertyValue = $reflProp->getValue($this->exif); + $this->assertSame($now, $propertyValue[$value]); + break; + case 'gps': + $coords = '40.333452380556,-20.167314813889'; + $setter = 'setGPS'; + $this->exif->$setter($coords); + $propertyValue = $reflProp->getValue($this->exif); + $this->assertEquals($coords, $propertyValue[$value]); + break; + case 'focalDistance': + $setter = 'setFocusDistance'; + default: + $this->exif->$setter($expected); + $propertyValue = $reflProp->getValue($this->exif); + $this->assertEquals($expected, $propertyValue[$value]); + break; + } + } + } + /** * Test that the values returned by both adapters are equal + * + * @group consistency + * @covers \PHPExif\Exif::getAperture + * @covers \PHPExif\Exif::getIso + * @covers \PHPExif\Exif::getExposure + * @covers \PHPExif\Exif::getExposureMilliseconds + * @covers \PHPExif\Exif::getFocusDistance + * @covers \PHPExif\Exif::getWidth + * @covers \PHPExif\Exif::getHeight + * @covers \PHPExif\Exif::getTitle + * @covers \PHPExif\Exif::getCaption + * @covers \PHPExif\Exif::getCopyright + * @covers \PHPExif\Exif::getKeywords + * @covers \PHPExif\Exif::getCamera + * @covers \PHPExif\Exif::getHorizontalResolution + * @covers \PHPExif\Exif::getVerticalResolution + * @covers \PHPExif\Exif::getSoftware + * @covers \PHPExif\Exif::getFocalLength + * @covers \PHPExif\Exif::getCreationDate + * @covers \PHPExif\Exif::getAuthor + * @covers \PHPExif\Exif::getCredit + * @covers \PHPExif\Exif::getSource + * @covers \PHPExif\Exif::getJobtitle + * @covers \PHPExif\Exif::getMimeType + * @covers \PHPExif\Exif::getFileSize */ public function testAdapterConsistency() { @@ -480,7 +615,7 @@ public function testAdapterConsistency() // find all Getter methods on the results and compare its output foreach ($methods as $method) { $name = $method->getName(); - if (strpos($name, 'get') !== 0 || $name == 'getRawData') { + if (strpos($name, 'get') !== 0 || $name == 'getRawData' || $name == 'getData') { continue; } $this->assertEquals( @@ -492,3 +627,4 @@ public function testAdapterConsistency() } } } + diff --git a/tests/PHPExif/Hydrator/MutatorTest.php b/tests/PHPExif/Hydrator/MutatorTest.php new file mode 100644 index 0000000..280188b --- /dev/null +++ b/tests/PHPExif/Hydrator/MutatorTest.php @@ -0,0 +1,69 @@ + + * @covers \PHPExif\Hydrator\HydratorInterface + */ +class MutatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * Setup function before the tests + */ + public function setUp() + { + } + + /** + * @group hydrator + * @covers \PHPExif\Hydrator\Mutator::hydrate + */ + public function testHydrateCallsDetermineMutator() + { + // input data + $input = array( + 'foo' => 'bar', + ); + + // create mock + $mock = $this->getMock('\\PHPExif\\Hydrator\\Mutator', array('determineMutator')); + + $mock->expects($this->exactly(count($input))) + ->method('determineMutator') + ->will($this->returnValue('setFoo')); + + $object = new TestClass(); + + // do the test + $mock->hydrate($object, $input); + } + + /** + * @group hydrator + * @covers \PHPExif\Hydrator\Mutator::hydrate + */ + public function testHydrateCallsMutatorsOnObject() + { + // input data + $input = array( + 'bar' => 'baz', + ); + + // create mock + $mock = $this->getMock('TestClass', array('setBar')); + + $mock->expects($this->once()) + ->method('setBar') + ->with($this->equalTo($input['bar'])); + + // do the test + $hydrator = new \PHPExif\Hydrator\Mutator; + $hydrator->hydrate($mock, $input); + } +} + +class TestClass +{ + public function setBar() + { + } +} + diff --git a/tests/PHPExif/Mapper/ExiftoolMapperTest.php b/tests/PHPExif/Mapper/ExiftoolMapperTest.php new file mode 100644 index 0000000..485cda8 --- /dev/null +++ b/tests/PHPExif/Mapper/ExiftoolMapperTest.php @@ -0,0 +1,239 @@ + + */ +class ExiftoolMapperTest extends \PHPUnit_Framework_TestCase +{ + protected $mapper; + + public function setUp() + { + $this->mapper = new \PHPExif\Mapper\Exiftool; + } + + /** + * @group mapper + */ + public function testClassImplementsCorrectInterface() + { + $this->assertInstanceOf('\\PHPExif\\Mapper\\MapperInterface', $this->mapper); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataIgnoresFieldIfItDoesntExist() + { + $rawData = array('foo' => 'bar'); + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertCount(0, $mapped); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataMapsFieldsCorrectly() + { + $reflProp = new \ReflectionProperty(get_class($this->mapper), 'map'); + $reflProp->setAccessible(true); + $map = $reflProp->getValue($this->mapper); + + // ignore custom formatted data stuff: + unset($map[\PHPExif\Mapper\Exiftool::APERTURE]); + unset($map[\PHPExif\Mapper\Exiftool::APPROXIMATEFOCUSDISTANCE]); + unset($map[\PHPExif\Mapper\Exiftool::CREATEDATE]); + unset($map[\PHPExif\Mapper\Exiftool::EXPOSURETIME]); + unset($map[\PHPExif\Mapper\Exiftool::FOCALLENGTH]); + unset($map[\PHPExif\Mapper\Exiftool::GPSLATITUDE]); + unset($map[\PHPExif\Mapper\Exiftool::GPSLONGITUDE]); + + // create raw data + $keys = array_keys($map); + $values = array(); + $values = array_pad($values, count($keys), 'foo'); + $rawData = array_combine($keys, $values); + + + $mapped = $this->mapper->mapRawData($rawData); + + $i = 0; + foreach ($mapped as $key => $value) { + $this->assertEquals($map[$keys[$i]], $key); + $i++; + } + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsAperture() + { + $rawData = array( + \PHPExif\Mapper\Exiftool::APERTURE => 0.123, + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals('f/0.1', reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsFocusDistance() + { + $rawData = array( + \PHPExif\Mapper\Exiftool::APPROXIMATEFOCUSDISTANCE => 50, + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals('50m', reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsCreationDate() + { + $rawData = array( + \PHPExif\Mapper\Exiftool::CREATEDATE => '2015:04:01 12:11:09', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $result = reset($mapped); + $this->assertInstanceOf('\\DateTime', $result); + $this->assertEquals( + reset($rawData), + $result->format('Y:m:d H:i:s') + ); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsExposureTime() + { + $rawData = array( + \PHPExif\Mapper\Exiftool::EXPOSURETIME => 1/400, + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals('1/400', reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsFocalLength() + { + $rawData = array( + \PHPExif\Mapper\Exiftool::FOCALLENGTH => '15 m', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals(15, reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsGPSData() + { + $this->mapper->setNumeric(false); + $result = $this->mapper->mapRawData( + array( + 'GPSLatitude' => '40 deg 20\' 0.42857" N', + 'GPSLatitudeRef' => 'North', + 'GPSLongitude' => '20 deg 10\' 2.33333" W', + 'GPSLongitudeRef' => 'West', + ) + ); + + $expected = '40.333452380556,-20.167314813889'; + $this->assertCount(1, $result); + $this->assertEquals($expected, reset($result)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyFormatsNumericGPSData() + { + $result = $this->mapper->mapRawData( + array( + 'GPSLatitude' => '40.333452381', + 'GPSLatitudeRef' => 'North', + 'GPSLongitude' => '20.167314814', + 'GPSLongitudeRef' => 'West', + ) + ); + + $expected = '40.333452381,-20.167314814'; + $this->assertCount(1, $result); + $this->assertEquals($expected, reset($result)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyIgnoresIncorrectGPSData() + { + $this->mapper->setNumeric(false); + $result = $this->mapper->mapRawData( + array( + 'GPSLatitude' => '40.333452381', + 'GPSLatitudeRef' => 'North', + 'GPSLongitude' => '20.167314814', + 'GPSLongitudeRef' => 'West', + ) + ); + + $this->assertCount(0, $result); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::mapRawData + */ + public function testMapRawDataCorrectlyIgnoresIncompleteGPSData() + { + $result = $this->mapper->mapRawData( + array( + 'GPSLatitude' => '40.333452381', + 'GPSLatitudeRef' => 'North', + ) + ); + + $this->assertCount(0, $result); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Exiftool::setNumeric + */ + public function testSetNumericInProperty() + { + $reflProperty = new \ReflectionProperty(get_class($this->mapper), 'numeric'); + $reflProperty->setAccessible(true); + + $expected = true; + $this->mapper->setNumeric($expected); + + $this->assertEquals($expected, $reflProperty->getValue($this->mapper)); + } +} diff --git a/tests/PHPExif/Mapper/NativeMapperTest.php b/tests/PHPExif/Mapper/NativeMapperTest.php new file mode 100644 index 0000000..d6e78fc --- /dev/null +++ b/tests/PHPExif/Mapper/NativeMapperTest.php @@ -0,0 +1,190 @@ + + */ +class NativeMapperTest extends \PHPUnit_Framework_TestCase +{ + protected $mapper; + + public function setUp() + { + $this->mapper = new \PHPExif\Mapper\Native; + } + + /** + * @group mapper + */ + public function testClassImplementsCorrectInterface() + { + $this->assertInstanceOf('\\PHPExif\\Mapper\\MapperInterface', $this->mapper); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataIgnoresFieldIfItDoesntExist() + { + $rawData = array('foo' => 'bar'); + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertCount(0, $mapped); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataMapsFieldsCorrectly() + { + $reflProp = new \ReflectionProperty(get_class($this->mapper), 'map'); + $reflProp->setAccessible(true); + $map = $reflProp->getValue($this->mapper); + + // ignore custom formatted data stuff: + unset($map[\PHPExif\Mapper\Native::DATETIMEORIGINAL]); + unset($map[\PHPExif\Mapper\Native::EXPOSURETIME]); + unset($map[\PHPExif\Mapper\Native::FOCALLENGTH]); + unset($map[\PHPExif\Mapper\Native::XRESOLUTION]); + unset($map[\PHPExif\Mapper\Native::YRESOLUTION]); + unset($map[\PHPExif\Mapper\Native::GPSLATITUDE]); + unset($map[\PHPExif\Mapper\Native::GPSLONGITUDE]); + + // create raw data + $keys = array_keys($map); + $values = array(); + $values = array_pad($values, count($keys), 'foo'); + $rawData = array_combine($keys, $values); + + + $mapped = $this->mapper->mapRawData($rawData); + + $i = 0; + foreach ($mapped as $key => $value) { + $this->assertEquals($map[$keys[$i]], $key); + $i++; + } + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsDateTimeOriginal() + { + $rawData = array( + \PHPExif\Mapper\Native::DATETIMEORIGINAL => '2015:04:01 12:11:09', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $result = reset($mapped); + $this->assertInstanceOf('\\DateTime', $result); + $this->assertEquals( + reset($rawData), + $result->format('Y:m:d H:i:s') + ); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsExposureTime() + { + $rawData = array( + \PHPExif\Mapper\Native::EXPOSURETIME => '2/800', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals('1/400', reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsFocalLength() + { + $rawData = array( + \PHPExif\Mapper\Native::FOCALLENGTH => '30/5', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals(6, reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsXResolution() + { + $rawData = array( + \PHPExif\Mapper\Native::XRESOLUTION => '1500/300', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals(1500, reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsYResolution() + { + $rawData = array( + \PHPExif\Mapper\Native::YRESOLUTION => '1500/300', + ); + + $mapped = $this->mapper->mapRawData($rawData); + + $this->assertEquals(1500, reset($mapped)); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataFlattensRawDataWithSections() + { + $rawData = array( + \PHPExif\Mapper\Native::SECTION_COMPUTED => array( + \PHPExif\Mapper\Native::TITLE => 'Hello', + ), + \PHPExif\Mapper\Native::HEADLINE => 'Headline', + ); + $mapped = $this->mapper->mapRawData($rawData); + $this->assertCount(2, $mapped); + $keys = array_keys($mapped); + + $expected = array( + \PHPExif\Mapper\Native::TITLE, + \PHPExif\Mapper\Native::HEADLINE + ); + $this->assertEquals($expected, $keys); + } + + /** + * @group mapper + * @covers \PHPExif\Mapper\Native::mapRawData + */ + public function testMapRawDataCorrectlyFormatsGPSData() + { + $result = $this->mapper->mapRawData( + array( + 'GPSLatitude' => array('40/1', '20/1', '15/35'), + 'GPSLatitudeRef' => 'N', + 'GPSLongitude' => array('20/1', '10/1', '35/15'), + 'GPSLongitudeRef' => 'W', + ) + ); + + $expected = '40.333452380952,-20.167314814815'; + $this->assertEquals($expected, reset($result)); + } +} diff --git a/tests/PHPExif/Reader/ReaderTest.php b/tests/PHPExif/Reader/ReaderTest.php index af546c4..82aa133 100644 --- a/tests/PHPExif/Reader/ReaderTest.php +++ b/tests/PHPExif/Reader/ReaderTest.php @@ -1,4 +1,9 @@ + * @covers \PHPExif\Reader\ReaderInterface + * @covers \PHPExif\Adapter\NoAdapterException + */ class ReaderTest extends \PHPUnit_Framework_TestCase { /** @@ -45,6 +50,21 @@ public function testGetAdapterFromProperty() $this->assertSame($mock, $this->reader->getAdapter()); } + /** + * @group reader + * @covers \PHPExif\Reader\Reader::getAdapter + * @covers \PHPExif\Adapter\NoAdapterException + * @expectedException \PHPExif\Adapter\NoAdapterException + */ + public function testGetAdapterThrowsExceptionWhenNoAdapterIsSet() + { + $reflProperty = new \ReflectionProperty('\PHPExif\Reader\Reader', 'adapter'); + $reflProperty->setAccessible(true); + $reflProperty->setValue($this->reader, null); + + $this->reader->getAdapter(); + } + /** * @group reader * @covers \PHPExif\Reader\Reader::read @@ -112,4 +132,30 @@ public function testFactoryAdapterTypeExiftool() $this->assertInstanceOf('\PHPExif\Adapter\Exiftool', $adapter); } + /** + * @group reader + * @covers \PHPExif\Reader\Reader::getExifFromFile + */ + public function testGetExifFromFileCallsReadMethod() + { + $mock = $this->getMock( + '\\PHPExif\\Reader\\Reader', + array('read'), + array(), + '', + false + ); + + $expected = '/foo/bar/baz'; + $expectedResult = 'test'; + + $mock->expects($this->once()) + ->method('read') + ->with($this->equalTo($expected)) + ->will($this->returnValue($expectedResult)); + + $result = $mock->getExifFromFile($expected); + $this->assertEquals($expectedResult, $result); + } } +