Skip to content

Commit b4322c9

Browse files
authored
Merge pull request #49 from rochamarcelo/feature/flysystem-renderer
Added flysystem renderer
2 parents 1c0ac54 + c0846cc commit b4322c9

File tree

4 files changed

+376
-1
lines changed

4 files changed

+376
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"cakedc/users": "^7.0"
3333
},
3434
"require-dev": {
35-
"phpunit/phpunit": "<6.0"
35+
"phpunit/phpunit": "<6.0",
36+
"league/flysystem-vfs": "^1.0"
3637
},
3738
"autoload": {
3839
"psr-4": {

config/api.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
'scopes' => ['api'],
7777
'levels' => ['error', 'info'],
7878
'file' => 'api.log',
79+
],
80+
'Flysystem' => [
81+
'expire' => '+1 day'
7982
]
8083
]
8184
];
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
/**
3+
* Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
4+
*
5+
* Licensed under The MIT License
6+
* Redistributions of files must retain the above copyright notice.
7+
*
8+
* @copyright Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
9+
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10+
*/
11+
12+
namespace CakeDC\Api\Service\Renderer;
13+
14+
use CakeDC\Api\Service\Action\Result;
15+
use Cake\Core\Configure;
16+
use Cake\Http\Response;
17+
use Cake\Log\LogTrait;
18+
use Cake\Utility\Hash;
19+
use Exception;
20+
use League\Flysystem\File;
21+
use League\Flysystem\FileNotFoundException;
22+
use League\Flysystem\Filesystem;
23+
use Zend\Diactoros\Stream;
24+
25+
/**
26+
* Class FlysystemRenderer to render file using Flysystem library
27+
*/
28+
class FlysystemRenderer extends FileRenderer
29+
{
30+
use LogTrait;
31+
/**
32+
* Builds the HTTP response.
33+
*
34+
* @param Result $result The result object returned by the Service.
35+
* @return bool
36+
*/
37+
public function response(Result $result = null)
38+
{
39+
$data = $result->getData();
40+
try {
41+
$file = $this->getFile(
42+
Hash::get($data, 'filesystem'),
43+
Hash::get($data, 'path')
44+
);
45+
46+
$this->_service->setResponse(
47+
$this->deliverAsset($this->_service->getResponse(), $file)
48+
);
49+
} catch (FileNotFoundException $e) {
50+
$response = $this->_service->getResponse()
51+
->withStatus(404);
52+
53+
$this->_service->setResponse($response);
54+
}
55+
56+
return true;
57+
}
58+
59+
/**
60+
* Get flystem file object
61+
*
62+
* @param Filesystem $filesystem custom filesystem
63+
* @param string $path of file at filesystem
64+
* @return File
65+
*/
66+
protected function getFile(Filesystem $filesystem, $path)
67+
{
68+
return $filesystem->get($path);
69+
}
70+
71+
/**
72+
* Deliver the asset stream in body
73+
*
74+
* @param Response $response service response
75+
* @param File $file file object
76+
* @return Response
77+
*/
78+
public function deliverAsset(Response $response, File $file)
79+
{
80+
$contentType = $file->getType();
81+
$modified = $file->getTimestamp();
82+
$expire = strtotime(Configure::write('Api.Flysystem.expire'));
83+
$maxAge = $expire - time();
84+
$stream = new Stream($file->readStream());
85+
86+
return $response->withBody($stream)
87+
// Content
88+
->withHeader('Content-Type', $contentType)
89+
// Cache
90+
->withHeader('Cache-Control', 'public,max-age=' . $maxAge)
91+
->withHeader('Date', gmdate('D, j M Y G:i:s \G\M\T', time()))
92+
->withHeader('Last-Modified', gmdate('D, j M Y G:i:s \G\M\T', $modified))
93+
->withHeader('Expires', gmdate('D, j M Y G:i:s \G\M\T', $expire));
94+
}
95+
96+
/**
97+
* Handle error setting a response status
98+
*
99+
* @param Exception $exception thrown at service or action
100+
*
101+
* @return void
102+
*/
103+
public function error(Exception $exception)
104+
{
105+
$code = $exception->getCode();
106+
$response = $this->_service->getResponse()
107+
->withStatus($code ? $code : 500);
108+
109+
$this->log($exception);
110+
111+
$this->_service->setResponse($response);
112+
}
113+
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
<?php
2+
/**
3+
* Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
4+
*
5+
* Licensed under The MIT License
6+
* Redistributions of files must retain the above copyright notice.
7+
*
8+
* @copyright Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
9+
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10+
*/
11+
12+
namespace CakeDC\Api\Test\TestCase\Service\Renderer;
13+
14+
use CakeDC\Api\Exception\UnauthenticatedException;
15+
use CakeDC\Api\Service\Action\Result;
16+
use CakeDC\Api\Service\FallbackService;
17+
use CakeDC\Api\Service\Renderer\FlysystemRenderer;
18+
use CakeDC\Api\Service\Service;
19+
use CakeDC\Api\TestSuite\TestCase;
20+
use CakeDC\Api\Test\ConfigTrait;
21+
use CakeDC\Api\Test\FixturesTrait;
22+
use Cake\Core\Configure;
23+
use League\Flysystem\Filesystem;
24+
use League\Flysystem\Vfs\VfsAdapter;
25+
use VirtualFileSystem\FileSystem as Vfs;
26+
27+
class FlysystemRendererTest extends TestCase
28+
{
29+
30+
use ConfigTrait;
31+
use FixturesTrait;
32+
33+
/**
34+
* @var Service
35+
*/
36+
public $Service;
37+
38+
/**
39+
* setUp method
40+
*
41+
* @return void
42+
*/
43+
public function setUp()
44+
{
45+
parent::setUp();
46+
47+
$this->_initializeRequest();
48+
}
49+
50+
/**
51+
* tearDown method
52+
*
53+
* @return void
54+
*/
55+
public function tearDown()
56+
{
57+
unset($this->Action);
58+
parent::tearDown();
59+
}
60+
61+
/**
62+
* Test initialize
63+
*
64+
* @return void
65+
*/
66+
public function testRendererInitializeByClassName()
67+
{
68+
$response = $this
69+
->getMockBuilder('Cake\Http\Response')
70+
->setMethods(['withStatus', 'withType', 'withStringBody'])
71+
->getMock();
72+
73+
$this->_initializeRequest([], 'GET', ['response' => $response]);
74+
$serviceOptions = [
75+
'version' => null,
76+
'request' => $this->request,
77+
'response' => $response,
78+
'rendererClass' => 'CakeDC/Api.Flysystem'
79+
];
80+
$this->Service = new FallbackService($serviceOptions);
81+
$renderer = $this->Service->getRenderer();
82+
$this->assertTrue($renderer instanceof FlysystemRenderer);
83+
}
84+
85+
/**
86+
* Test render response
87+
*
88+
* @return void
89+
*/
90+
public function testRendererSuccess()
91+
{
92+
Configure::write('debug', 0);
93+
$response = $this
94+
->getMockBuilder('Cake\Http\Response')
95+
->setMethods(['withStatus', 'withType', 'withStringBody'])
96+
->getMock();
97+
98+
$this->_initializeRequest([], 'GET', ['response' => $response]);
99+
$serviceOptions = [
100+
'version' => null,
101+
'request' => $this->request,
102+
'response' => $response,
103+
'rendererClass' => 'CakeDC/Api.Flysystem'
104+
];
105+
$this->Service = new FallbackService($serviceOptions);
106+
107+
$result = new Result();
108+
$statusCode = 200;
109+
$result->setCode($statusCode);
110+
111+
$vfs = new Vfs();
112+
$path = "/my-file.zip";
113+
$vfs->createFile($path, 'content-for-download');
114+
115+
$filesystem = new Filesystem(new VfsAdapter($vfs));
116+
$data = compact('filesystem', 'path');
117+
$result->setData($data);
118+
$renderer = $this->Service->getRenderer();
119+
120+
$renderer->response($result);
121+
122+
$headers = $this->Service->getResponse()->getHeaders();
123+
$this->assertEquals(
124+
['file'],
125+
$headers['Content-Type']
126+
);
127+
$this->assertArrayHasKey('Cache-Control', $headers);
128+
$this->assertArrayHasKey('Date', $headers);
129+
$this->assertArrayHasKey('Last-Modified', $headers);
130+
$this->assertArrayHasKey('Expires', $headers);
131+
}
132+
133+
/**
134+
* Test render response
135+
*
136+
* @return void
137+
*/
138+
public function testRendereFileNotFound()
139+
{
140+
Configure::write('debug', 0);
141+
$response = $this
142+
->getMockBuilder('Cake\Http\Response')
143+
->setMethods(['withStatus', 'withType', 'withStringBody'])
144+
->getMock();
145+
146+
$this->_initializeRequest([], 'GET', ['response' => $response]);
147+
$serviceOptions = [
148+
'version' => null,
149+
'request' => $this->request,
150+
'response' => $response,
151+
'rendererClass' => 'CakeDC/Api.Flysystem'
152+
];
153+
$this->Service = new FallbackService($serviceOptions);
154+
155+
$result = new Result();
156+
$statusCode = 200;
157+
$result->setCode($statusCode);
158+
159+
$vfs = new Vfs();
160+
$path = "/my-file-not-found.zip";
161+
162+
$filesystem = new Filesystem(new VfsAdapter($vfs));
163+
$data = compact('filesystem', 'path');
164+
$result->setData($data);
165+
$renderer = $this->Service->getRenderer();
166+
$response->expects($this->once())
167+
->method('withStatus')
168+
->with(404)
169+
->will($this->returnValue($response));
170+
$response->expects($this->never())
171+
->method('withType');
172+
$response->expects($this->never())
173+
->method('withStringBody');
174+
175+
$renderer->response($result);
176+
177+
$headers = $this->Service->getResponse()->getHeaders();
178+
$this->assertEquals(
179+
['text/html; charset=UTF-8'],
180+
$headers['Content-Type']
181+
);
182+
$this->assertEmpty((string)$this->Service->getResponse()->getBody());
183+
}
184+
185+
/**
186+
* Test render error
187+
*
188+
* @return void
189+
*/
190+
public function testRendererError()
191+
{
192+
$response = $this
193+
->getMockBuilder('Cake\Http\Response')
194+
->setMethods(['withStatus', 'withType', 'withStringBody'])
195+
->getMock();
196+
197+
$this->_initializeRequest([], 'GET', ['response' => $response]);
198+
$serviceOptions = [
199+
'version' => null,
200+
'request' => $this->request,
201+
'response' => $response,
202+
'rendererClass' => 'CakeDC/Api.Flysystem'
203+
];
204+
$this->Service = new FallbackService($serviceOptions);
205+
206+
Configure::write('debug', 0);
207+
$error = new UnauthenticatedException();
208+
$renderer = $this->Service->getRenderer();
209+
210+
$response->expects($this->once())
211+
->method('withStatus')
212+
->with(401)
213+
->will($this->returnValue($response));
214+
$response->expects($this->never())
215+
->method('withType');
216+
$response->expects($this->never())
217+
->method('withStringBody');
218+
219+
$renderer->error($error);
220+
}
221+
222+
/**
223+
* Test render error
224+
*
225+
* @return void
226+
*/
227+
public function testRendererErrorEmptyExceptionCode()
228+
{
229+
$response = $this
230+
->getMockBuilder('Cake\Http\Response')
231+
->setMethods(['withStatus', 'withType', 'withStringBody'])
232+
->getMock();
233+
234+
$this->_initializeRequest([], 'GET', ['response' => $response]);
235+
$serviceOptions = [
236+
'version' => null,
237+
'request' => $this->request,
238+
'response' => $response,
239+
'rendererClass' => 'CakeDC/Api.Flysystem'
240+
];
241+
$this->Service = new FallbackService($serviceOptions);
242+
243+
Configure::write('debug', 0);
244+
$error = new \Exception();
245+
$renderer = $this->Service->getRenderer();
246+
247+
$response->expects($this->once())
248+
->method('withStatus')
249+
->with(500)
250+
->will($this->returnValue($response));
251+
$response->expects($this->never())
252+
->method('withType');
253+
$response->expects($this->never())
254+
->method('withStringBody');
255+
256+
$renderer->error($error);
257+
}
258+
}

0 commit comments

Comments
 (0)