Skip to content

Commit f76623e

Browse files
committed
API-487: Isolate files manipulation in a dedicated service
1 parent f338e95 commit f76623e

File tree

7 files changed

+161
-25
lines changed

7 files changed

+161
-25
lines changed

spec/Api/ProductMediaFileApiSpec.php

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
use Akeneo\Pim\ApiClient\Api\MediaFileApiInterface;
1010
use Akeneo\Pim\ApiClient\Client\ResourceClientInterface;
1111
use Akeneo\Pim\ApiClient\Exception\RuntimeException;
12+
use Akeneo\Pim\ApiClient\FileSystem\FileSystemInterface;
1213
use Akeneo\Pim\ApiClient\Pagination\PageInterface;
1314
use Akeneo\Pim\ApiClient\Pagination\PageFactoryInterface;
1415
use Akeneo\Pim\ApiClient\Pagination\ResourceCursorFactoryInterface;
1516
use Akeneo\Pim\ApiClient\Pagination\ResourceCursorInterface;
1617
use PhpSpec\ObjectBehavior;
18+
use Prophecy\Argument;
1719
use Psr\Http\Message\ResponseInterface;
1820
use Psr\Http\Message\StreamInterface;
1921

@@ -22,9 +24,10 @@ class ProductMediaFileApiSpec extends ObjectBehavior
2224
function let(
2325
ResourceClientInterface $resourceClient,
2426
PageFactoryInterface $pageFactory,
25-
ResourceCursorFactoryInterface $cursorFactory
27+
ResourceCursorFactoryInterface $cursorFactory,
28+
FileSystemInterface $fileSystem
2629
) {
27-
$this->beConstructedWith($resourceClient, $pageFactory, $cursorFactory);
30+
$this->beConstructedWith($resourceClient, $pageFactory, $cursorFactory, $fileSystem);
2831
}
2932

3033
function it_is_initializable()
@@ -103,9 +106,11 @@ function it_returns_a_list_of_media_files_with_additional_query_parameters($reso
103106
$this->listPerPage(null, null, ['foo' => 'bar'])->shouldReturn($page);
104107
}
105108

106-
function it_creates_a_media_file($resourceClient, ResponseInterface $response)
109+
function it_creates_a_media_file_from_a_path($resourceClient, $fileSystem, ResponseInterface $response)
107110
{
108111
$fileResource = fopen('php://stdin', 'r');
112+
$fileSystem->getResourceFromPath('/images/akeneo.png')->willReturn($fileResource);
113+
109114
$product = [
110115
'identifier' => 'foo',
111116
'attribute' => 'picture',
@@ -132,23 +137,43 @@ function it_creates_a_media_file($resourceClient, ResponseInterface $response)
132137
->createMultipartResource(ProductMediaFileApi::MEDIA_FILES_URI, [], $requestParts)
133138
->willReturn($response);
134139

135-
$this->create($fileResource, $product)
140+
$this->create('/images/akeneo.png', $product)
136141
->shouldReturn('1/e/e/d/1eed10f108bde68b279d6f903f17b4b053e9d89d_akeneo.png');
137142
}
138143

139-
function it_throws_an_exception_if_the_file_is_unreadable_when_creating_a_media_file()
144+
function it_creates_a_media_file_from_a_resource($resourceClient, $fileSystem, ResponseInterface $response)
140145
{
141-
$this
142-
->shouldThrow(new RuntimeException('The file "/foo.bar" could not be read.'))
143-
->during('create', [
144-
'/foo.bar',
145-
[
146-
'identifier' => 'foo',
147-
'attribute' => 'picture',
148-
'scope' => 'e-commerce',
149-
'locale' => 'en_US',
150-
]
151-
]);
146+
$fileResource = fopen('php://stdin', 'r');
147+
$fileSystem->getResourceFromPath(Argument::any())->shouldNotBeCalled();
148+
149+
$product = [
150+
'identifier' => 'foo',
151+
'attribute' => 'picture',
152+
'scope' => 'e-commerce',
153+
'locale' => 'en_US',
154+
];
155+
156+
$requestParts = [
157+
[
158+
'name' => 'product',
159+
'contents' => json_encode($product),
160+
],
161+
[
162+
'name' => 'file',
163+
'contents' => $fileResource,
164+
]
165+
];
166+
167+
$response->getHeaders()->willReturn(['Location' => [
168+
'http://localhost/api/rest/v1/media-files/1/e/e/d/1eed10f108bde68b279d6f903f17b4b053e9d89d_akeneo.png'
169+
]]);
170+
171+
$resourceClient
172+
->createMultipartResource(ProductMediaFileApi::MEDIA_FILES_URI, [], $requestParts)
173+
->willReturn($response);
174+
175+
$this->create($fileResource, $product)
176+
->shouldReturn('1/e/e/d/1eed10f108bde68b279d6f903f17b4b053e9d89d_akeneo.png');
152177
}
153178

154179
function it_throws_an_exception_if_the_response_does_not_contain_the_uri_of_the_created_media_file($resourceClient, ResponseInterface $response)

src/AkeneoPimClientBuilder.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use Akeneo\Pim\ApiClient\Client\AuthenticatedHttpClient;
2121
use Akeneo\Pim\ApiClient\Client\HttpClient;
2222
use Akeneo\Pim\ApiClient\Client\ResourceClient;
23+
use Akeneo\Pim\ApiClient\FileSystem\FileSystemInterface;
24+
use Akeneo\Pim\ApiClient\FileSystem\LocalFileSystem;
2325
use Akeneo\Pim\ApiClient\Pagination\PageFactory;
2426
use Akeneo\Pim\ApiClient\Pagination\ResourceCursorFactory;
2527
use Akeneo\Pim\ApiClient\Routing\UriGenerator;
@@ -55,6 +57,9 @@ class AkeneoPimClientBuilder
5557
/** @var StreamFactory */
5658
protected $streamFactory;
5759

60+
/** @var FileSystemInterface */
61+
protected $fileSystem;
62+
5863
/**
5964
* @param string $baseUri Base uri to request the API
6065
*/
@@ -105,6 +110,20 @@ public function setStreamFactory($streamFactory)
105110
return $this;
106111
}
107112

113+
/**
114+
* Allows to define another implementation than LocalFileSystem
115+
*
116+
* @param FileSystemInterface $fileSystem
117+
*
118+
* @return AkeneoPimClientBuilder
119+
*/
120+
public function setFileSystem($fileSystem)
121+
{
122+
$this->fileSystem = $fileSystem;
123+
124+
return $this;
125+
}
126+
108127
/**
109128
* Build the Akeneo PIM client authenticated by user name and password.
110129
*
@@ -146,7 +165,7 @@ public function buildAuthenticatedByToken($clientId, $secret, $token, $refreshTo
146165
*/
147166
protected function buildAuthenticatedClient(Authentication $authentication)
148167
{
149-
list($resourceClient, $pageFactory, $cursorFactory) = $this->setUp($authentication);
168+
list($resourceClient, $pageFactory, $cursorFactory, $fileSystem) = $this->setUp($authentication);
150169

151170
$client = new AkeneoPimClient(
152171
$authentication,
@@ -156,7 +175,7 @@ protected function buildAuthenticatedClient(Authentication $authentication)
156175
new AttributeOptionApi($resourceClient, $pageFactory, $cursorFactory),
157176
new AttributeGroupApi($resourceClient, $pageFactory, $cursorFactory),
158177
new FamilyApi($resourceClient, $pageFactory, $cursorFactory),
159-
new ProductMediaFileApi($resourceClient, $pageFactory, $cursorFactory),
178+
new ProductMediaFileApi($resourceClient, $pageFactory, $cursorFactory, $fileSystem),
160179
new LocaleApi($resourceClient, $pageFactory, $cursorFactory),
161180
new ChannelApi($resourceClient, $pageFactory, $cursorFactory),
162181
new CurrencyApi($resourceClient, $pageFactory, $cursorFactory),
@@ -193,8 +212,9 @@ protected function setUp(Authentication $authentication)
193212

194213
$pageFactory = new PageFactory($authenticatedHttpClient);
195214
$cursorFactory = new ResourceCursorFactory();
215+
$fileSystem = null !== $this->fileSystem ? $this->fileSystem : new LocalFileSystem();
196216

197-
return [$resourceClient, $pageFactory, $cursorFactory];
217+
return [$resourceClient, $pageFactory, $cursorFactory, $fileSystem];
198218
}
199219

200220
/**

src/Api/ProductMediaFileApi.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Akeneo\Pim\ApiClient\Client\ResourceClientInterface;
66
use Akeneo\Pim\ApiClient\Exception\RuntimeException;
7+
use Akeneo\Pim\ApiClient\FileSystem\FileSystemInterface;
78
use Akeneo\Pim\ApiClient\Pagination\PageFactoryInterface;
89
use Akeneo\Pim\ApiClient\Pagination\ResourceCursorFactoryInterface;
910
use Psr\Http\Message\ResponseInterface;
@@ -31,19 +32,25 @@ class ProductMediaFileApi implements MediaFileApiInterface
3132
/** @var ResourceCursorFactoryInterface */
3233
protected $cursorFactory;
3334

35+
/** @var FileSystemInterface */
36+
private $fileSystem;
37+
3438
/**
3539
* @param ResourceClientInterface $resourceClient
3640
* @param PageFactoryInterface $pageFactory
3741
* @param ResourceCursorFactoryInterface $cursorFactory
42+
* @param FileSystemInterface $fileSystem
3843
*/
3944
public function __construct(
4045
ResourceClientInterface $resourceClient,
4146
PageFactoryInterface $pageFactory,
42-
ResourceCursorFactoryInterface $cursorFactory
47+
ResourceCursorFactoryInterface $cursorFactory,
48+
FileSystemInterface $fileSystem
4349
) {
4450
$this->resourceClient = $resourceClient;
4551
$this->pageFactory = $pageFactory;
4652
$this->cursorFactory = $cursorFactory;
53+
$this->fileSystem = $fileSystem;
4754
}
4855

4956
/**
@@ -80,11 +87,7 @@ public function all($pageSize = 10, array $queryParameters = [])
8087
public function create($mediaFile, array $productData)
8188
{
8289
if (is_string($mediaFile)) {
83-
if (!is_readable($mediaFile)) {
84-
throw new RuntimeException(sprintf('The file "%s" could not be read.', $mediaFile));
85-
}
86-
87-
$mediaFile = fopen($mediaFile, 'rb');
90+
$mediaFile = $this->fileSystem->getResourceFromPath($mediaFile);
8891
}
8992

9093
$requestParts = [
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Akeneo\Pim\ApiClient\Exception;
4+
5+
/**
6+
* Exception thrown when a file can not be read.
7+
*
8+
* @author Laurent Petard <[email protected]>
9+
* @copyright 2017 Akeneo SAS (http://www.akeneo.com)
10+
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
11+
*/
12+
class UnreadableFileException extends RuntimeException
13+
{
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Akeneo\Pim\ApiClient\FileSystem;
4+
5+
use Akeneo\Pim\ApiClient\Exception\UnreadableFileException;
6+
7+
/**
8+
* Manipulates files for the API.
9+
*
10+
* @author Laurent Petard <[email protected]>
11+
* @copyright 2017 Akeneo SAS (http://www.akeneo.com)
12+
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
13+
*/
14+
interface FileSystemInterface
15+
{
16+
/**
17+
* Gets the resource of a file from its path.
18+
*
19+
* @param string $filePath Path of the file
20+
*
21+
* @throws UnreadableFileException if the file doesn't exists or is not readable
22+
*
23+
* @return resource
24+
*/
25+
public function getResourceFromPath($filePath);
26+
}

src/FileSystem/LocalFileSystem.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Akeneo\Pim\ApiClient\FileSystem;
4+
5+
use Akeneo\Pim\ApiClient\Exception\UnreadableFileException;
6+
7+
/**
8+
* File system to manipulate files stored locally.
9+
*
10+
* @author Laurent Petard <[email protected]>
11+
* @copyright 2017 Akeneo SAS (http://www.akeneo.com)
12+
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
13+
*/
14+
class LocalFileSystem implements FileSystemInterface
15+
{
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
public function getResourceFromPath($filePath)
20+
{
21+
if (!is_readable($filePath)) {
22+
throw new UnreadableFileException(sprintf('The file "%s" could not be read.', $filePath));
23+
}
24+
25+
$fileResource = fopen($filePath, 'rb');
26+
27+
if (!is_resource($fileResource)) {
28+
throw new \RuntimeException(sprintf('The file "%s" could not be opened.', $filePath));
29+
}
30+
31+
return $fileResource;
32+
}
33+
}

tests/Common/Api/ProductMediaFile/CreateProductMediaFileApiIntegration.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ public function testCreateWithAnInvalidRequest()
8787
]);
8888
}
8989

90+
/**
91+
* @expectedException \Akeneo\Pim\ApiClient\Exception\UnreadableFileException
92+
*/
93+
public function testCreateWithAnInvalidFile()
94+
{
95+
$api = $this->createClient()->getProductMediaFileApi();
96+
97+
$api->create('foo.jpg', [
98+
'identifier' => 'medium_boot',
99+
'attribute' => 'side_view',
100+
'scope' => null,
101+
'locale' => null,
102+
]);
103+
}
104+
90105
/**
91106
* Sanitize the code and links of a media file, because the code is generated randomly.
92107
*

0 commit comments

Comments
 (0)