Skip to content

Commit e617767

Browse files
committed
Exiftool Reader class & unit tests
Signed-off-by: Tom Van Herreweghe <[email protected]>
1 parent 812fa4c commit e617767

File tree

2 files changed

+284
-11
lines changed

2 files changed

+284
-11
lines changed

src/Reader.php

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @package Exiftool
1010
*/
1111

12-
namespace PHPExif\Adapter\Native;
12+
namespace PHPExif\Adapter\Exiftool;
1313

1414
use PHPExif\Common\Adapter\MapperInterface;
1515
use PHPExif\Common\Adapter\ReaderInterface;
@@ -31,6 +31,7 @@ final class Reader implements ReaderInterface
3131
{
3232
const PATH = 'path';
3333
const BIN = 'binary';
34+
const NUMERIC = 'numeric';
3435

3536
/**
3637
* @var MapperInterface
@@ -45,7 +46,7 @@ final class Reader implements ReaderInterface
4546
/**
4647
* @var bool
4748
*/
48-
private $numeric = true;
49+
private $numeric;
4950

5051
/**
5152
* @var string
@@ -62,11 +63,13 @@ public function __construct(
6263
$defaults = [
6364
self::BIN => 'exiftool',
6465
self::PATH => '/usr/bin/env',
66+
self::NUMERIC => true,
6567
];
6668
$config = array_replace($defaults, $config);
6769

6870
$this->binary = $config[self::BIN];
6971
$this->path = $config[self::PATH];
72+
$this->numeric = $config[self::NUMERIC];
7073

7174
$this->mapper = $mapper;
7275
}
@@ -94,12 +97,13 @@ public function getMetadataFromFile($filePath)
9497
)
9598
);
9699

97-
$data = json_decode($result, true);
98-
99-
if (false === $data) {
100+
if (false === $result) {
100101
throw NoExifDataException::fromFile($filePath);
101102
}
102103

104+
$data = json_decode($result, true)[0];
105+
$data = $this->normalizeArrayKeys($data);
106+
103107
// map the data:
104108
$mapper = $this->getMapper();
105109
$metadata = new Metadata(
@@ -111,14 +115,38 @@ public function getMetadataFromFile($filePath)
111115
return $metadata;
112116
}
113117

118+
/**
119+
* Lowercases the keys for given array
120+
*
121+
* @param array $data
122+
*
123+
* @return array
124+
*/
125+
private function normalizeArrayKeys(array $data)
126+
{
127+
$keys = array_keys($data);
128+
$keys = array_map('strtolower', $keys);
129+
$values = array_values($data);
130+
$values = array_map(function ($value) {
131+
if (!is_array($value)) {
132+
return $value;
133+
}
134+
135+
return $this->normalizeArrayKeys($value);
136+
}, $values);
137+
138+
return array_combine(
139+
$keys,
140+
$values
141+
);
142+
}
143+
114144
/**
115145
* Returns the output from given cli command
116146
*
117147
* @param string $command
118148
*
119-
* @throws RuntimeException If the command can't be executed
120-
*
121-
* @return string
149+
* @return string|boolean
122150
*/
123151
protected function getCliOutput($command)
124152
{
@@ -128,11 +156,11 @@ protected function getCliOutput($command)
128156
2 => array('pipe', 'a')
129157
);
130158
$process = proc_open($command, $descriptorspec, $pipes);
159+
131160
if (!is_resource($process)) {
132-
throw new RuntimeException(
133-
'Could not open a resource to the exiftool binary'
134-
);
161+
return false;
135162
}
163+
136164
$result = stream_get_contents($pipes[1]);
137165
fclose($pipes[0]);
138166
fclose($pipes[1]);

tests/unit/ReaderTest.php

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<?php
2+
namespace
3+
{
4+
$mockProcOpen = false;
5+
$mockStreamGetContents = false;
6+
}
7+
8+
namespace PHPExif\Adapter\Exiftool
9+
{
10+
use Mockery as m;
11+
use PHPExif\Adapter\Exiftool\Reader;
12+
use PHPExif\Common\Adapter\MapperInterface;
13+
use PHPExif\Common\Data\Metadata;
14+
use PHPExif\Common\Exception\Reader\NoExifDataException;
15+
use \ReflectionProperty;
16+
17+
// stub the function
18+
function proc_open($cmd, array $descriptorspec, &$pipes, $cwd = null, array $env = [], array $other_options = [])
19+
{
20+
global $mockProcOpen;
21+
22+
switch ($mockProcOpen) {
23+
case -10:
24+
return false;
25+
case false:
26+
default:
27+
return \proc_open($cmd, $descriptorspec, $pipes, $cwd, $env, $other_options);
28+
}
29+
}
30+
31+
function stream_get_contents($handle, $maxlength = -1, $offset = -1)
32+
{
33+
global $mockStreamGetContents;
34+
35+
switch ($mockStreamGetContents) {
36+
case -10:
37+
return false;
38+
case -20:
39+
return '[{"foo":"bar"}]';
40+
case false:
41+
default:
42+
return call_user_func_array('stream_get_contents', func_get_args());
43+
}
44+
}
45+
46+
47+
/**
48+
* Class: ReaderTest
49+
*
50+
* @see \PHPUnit_Framework_TestCase
51+
* @coversDefaultClass \PHPExif\Adapter\Exiftool\Reader
52+
* @covers ::<!public>
53+
*/
54+
class ReaderTest extends \PHPUnit_Framework_TestCase
55+
{
56+
public function setUp()
57+
{
58+
global $mockProcOpen;
59+
global $mockStreamGetContents;
60+
61+
$mockProcOpen = false;
62+
$mockStreamGetContents = false;
63+
}
64+
65+
/**
66+
* @covers ::__construct
67+
* @dataProvider defaultPropertyValues
68+
* @group reader
69+
*
70+
* @return void
71+
*/
72+
public function testConstructorSetsDefaultConfiguration($propertyName, $expectedValue)
73+
{
74+
$mapper = m::mock(MapperInterface::class);
75+
$reader = new Reader($mapper);
76+
77+
$reflProp = new ReflectionProperty(Reader::class, $propertyName);
78+
$reflProp->setAccessible(true);
79+
80+
$this->assertEquals(
81+
$expectedValue,
82+
$reflProp->getValue($reader)
83+
);
84+
}
85+
86+
/**
87+
* @return array
88+
*/
89+
public function defaultPropertyValues()
90+
{
91+
return [
92+
[
93+
'binary',
94+
'exiftool'
95+
],
96+
[
97+
'numeric',
98+
true
99+
],
100+
[
101+
'path',
102+
'/usr/bin/env'
103+
],
104+
];
105+
}
106+
107+
/**
108+
* @covers ::__construct
109+
* @dataProvider overrideDefaultPropertyValues
110+
* @group reader
111+
*
112+
* @return void
113+
*/
114+
public function testConstructorCanOverrideDefaultConfiguration($propertyName, $defaultValue, $key, $newValue)
115+
{
116+
$mapper = m::mock(MapperInterface::class);
117+
$reader = new Reader($mapper);
118+
119+
$reflProp = new ReflectionProperty(Reader::class, $propertyName);
120+
$reflProp->setAccessible(true);
121+
122+
$this->assertEquals(
123+
$defaultValue,
124+
$reflProp->getValue($reader)
125+
);
126+
127+
$reader = new Reader($mapper, [$key => $newValue]);
128+
129+
$this->assertEquals(
130+
$newValue,
131+
$reflProp->getValue($reader)
132+
);
133+
}
134+
135+
/**
136+
* @return array
137+
*/
138+
public function overrideDefaultPropertyValues()
139+
{
140+
return [
141+
[
142+
'binary',
143+
'exiftool',
144+
Reader::BIN,
145+
'exiftool2',
146+
],
147+
[
148+
'numeric',
149+
true,
150+
Reader::NUMERIC,
151+
false,
152+
],
153+
[
154+
'path',
155+
'/usr/bin/env',
156+
Reader::PATH,
157+
'/dev/null',
158+
],
159+
];
160+
}
161+
162+
/**
163+
* @covers ::getMapper
164+
* @group reader
165+
*
166+
* @return void
167+
*/
168+
public function testGetMapperReturnsMapper()
169+
{
170+
$mapper = m::mock(MapperInterface::class);
171+
$reader = new Reader($mapper);
172+
173+
$this->assertSame(
174+
$mapper,
175+
$reader->getMapper()
176+
);
177+
}
178+
179+
/**
180+
* @covers ::getMetadataFromFile
181+
* @group reader
182+
*
183+
* @return void
184+
*/
185+
public function testGetMetadataFromFileThrowsExceptionWhenNoProcOpen()
186+
{
187+
global $mockProcOpen;
188+
$mockProcOpen = -10;
189+
190+
$this->setExpectedException(NoExifDataException::class);
191+
$mapper = m::mock(MapperInterface::class);
192+
$reader = new Reader($mapper);
193+
194+
$reader->getMetadataFromFile('/dev/null');
195+
}
196+
197+
/**
198+
* @covers ::getMetadataFromFile
199+
* @group reader
200+
*
201+
* @return void
202+
*/
203+
public function testGetMetadataFromFileThrowsExceptionWhenNoData()
204+
{
205+
global $mockStreamGetContents;
206+
$mockStreamGetContents = -10;
207+
208+
$this->setExpectedException(NoExifDataException::class);
209+
210+
$mapper = m::mock(MapperInterface::class);
211+
$mapper->shouldReceive('map')
212+
->andReturnNull();
213+
$reader = new Reader($mapper);
214+
$result = $reader->getMetadataFromFile('/tmp');
215+
216+
$this->assertInstanceOf(
217+
Metadata::class,
218+
$result
219+
);
220+
}
221+
222+
/**
223+
* @covers ::getMetadataFromFile
224+
* @group reader
225+
*
226+
* @return void
227+
*/
228+
public function testGetMetadataFromFileReturnsMetadataInstance()
229+
{
230+
global $mockStreamGetContents;
231+
$mockStreamGetContents = -20;
232+
233+
$mapper = m::mock(MapperInterface::class);
234+
$mapper->shouldReceive('map')
235+
->andReturnNull();
236+
$reader = new Reader($mapper);
237+
$result = $reader->getMetadataFromFile('/tmp');
238+
239+
$this->assertInstanceOf(
240+
Metadata::class,
241+
$result
242+
);
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)