Skip to content

Commit 7d993d8

Browse files
committed
feat: Add screenshot support
1 parent b6ca88f commit 7d993d8

5 files changed

Lines changed: 184 additions & 91 deletions

File tree

src/Browserless.php

Lines changed: 5 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,20 @@
22

33
namespace SynergiTech\ChromePDF;
44

5-
use GuzzleHttp\Psr7\StreamWrapper;
5+
use SynergiTech\ChromePDF\Browserless\Client;
66

77
/**
88
* Driver to render PDFs remotely using browserless.io
99
*/
1010
class Browserless extends AbstractPDF
1111
{
12-
/**
13-
* @var string|null
14-
*/
15-
private $apiKey;
16-
/**
17-
* @var string
18-
*/
19-
private $apiUrl = 'https://chrome.browserless.io';
12+
use Client;
13+
2014
/**
2115
* @var string
2216
*/
2317
private $pdfEndpoint = '/pdf';
24-
/**
25-
* @var \GuzzleHttp\Client
26-
*/
27-
private $client;
18+
2819
/**
2920
* @var bool
3021
*/
@@ -38,25 +29,6 @@ class Browserless extends AbstractPDF
3829
*/
3930
private $timeout;
4031

41-
/**
42-
* @param string $apiKey api key from browserless.io
43-
* @param \GuzzleHttp\Client $client custom Guzzle client
44-
*/
45-
public function __construct(string $apiKey = null, $client = null)
46-
{
47-
if ($client === null) {
48-
// @codeCoverageIgnoreStart
49-
$client = new \GuzzleHttp\Client([
50-
'base_uri' => $this->apiUrl,
51-
]);
52-
// @codeCoverageIgnoreEnd
53-
}
54-
$this->client = $client;
55-
if ($apiKey !== null) {
56-
$this->setApiKey($apiKey);
57-
}
58-
}
59-
6032
/**
6133
* Sets the PDF documents rotation
6234
*
@@ -69,18 +41,6 @@ public function setRotation(int $rotation = null): self
6941
return $this;
7042
}
7143

72-
/**
73-
* Sets the browserless API key
74-
*
75-
* @param string $apiKey
76-
* @return self
77-
*/
78-
public function setApiKey(string $apiKey): self
79-
{
80-
$this->apiKey = $apiKey;
81-
return $this;
82-
}
83-
8444
/**
8545
* Sets whether or not to ask Browserless to attempt to render the document in safe mode
8646
*
@@ -116,16 +76,6 @@ public function getTimeout(): ?int
11676
return $this->timeout;
11777
}
11878

119-
/**
120-
* Retrieves the browserless.io API key
121-
*
122-
* @return string|null
123-
*/
124-
public function getApiKey(): ?string
125-
{
126-
return $this->apiKey;
127-
}
128-
12979
/**
13080
* Whether the document will be rendered in safe mode or not
13181
*
@@ -232,41 +182,7 @@ public function getFormattedOptions(): array
232182
*/
233183
private function render(array $options)
234184
{
235-
try {
236-
$response = $this->client->post($this->pdfEndpoint, [
237-
'query' => [
238-
'token' => $this->getApiKey(),
239-
],
240-
'json' => $options,
241-
]);
242-
} catch (\GuzzleHttp\Exception\ClientException $e) {
243-
$message = 'No response';
244-
245-
$response = $e->getResponse();
246-
247-
/**
248-
* You could use $e->hasResponse() but that is not accurate enough,
249-
* as phpstan will be analysing against method signatures from guzzle 6 & 7
250-
*/
251-
if ($response !== null) {
252-
$message = $response->getBody();
253-
254-
$json = json_decode($message);
255-
if (json_last_error() === JSON_ERROR_NONE) {
256-
$messages = [];
257-
foreach ($json as $error) {
258-
$messages[] = $error->message;
259-
}
260-
$message = implode(', ', $messages);
261-
}
262-
}
263-
264-
throw new Browserless\APIException("Failed to render PDF: {$message}", $e->getCode(), $e);
265-
} catch (\Exception $e) {
266-
throw new Browserless\APIException("Failed to render PDF: {$e->getMessage()}", $e->getCode(), $e);
267-
}
268-
269-
return StreamWrapper::getResource($response->getBody());
185+
return $this->request($this->pdfEndpoint, $options);
270186
}
271187

272188
/**

src/Browserless/Client.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace SynergiTech\ChromePDF\Browserless;
4+
5+
use GuzzleHttp\Psr7\StreamWrapper;
6+
7+
trait Client
8+
{
9+
/**
10+
* @var \GuzzleHttp\Client
11+
*/
12+
private $client;
13+
14+
/**
15+
* @var string|null
16+
*/
17+
private $apiKey;
18+
19+
/**
20+
* @var string
21+
*/
22+
private $apiUrl = 'https://chrome.browserless.io';
23+
24+
/**
25+
* @param string $apiKey api key from browserless.io
26+
* @param \GuzzleHttp\Client $client custom Guzzle client
27+
*/
28+
public function __construct(string $apiKey = null, $client = null)
29+
{
30+
if ($client === null) {
31+
// @codeCoverageIgnoreStart
32+
$client = new \GuzzleHttp\Client([
33+
'base_uri' => $this->apiUrl,
34+
]);
35+
// @codeCoverageIgnoreEnd
36+
}
37+
$this->client = $client;
38+
if ($apiKey !== null) {
39+
$this->setApiKey($apiKey);
40+
}
41+
}
42+
43+
/**
44+
* Retrieves the browserless.io API key
45+
*
46+
* @return string|null
47+
*/
48+
public function getApiKey(): ?string
49+
{
50+
return $this->apiKey;
51+
}
52+
53+
/**
54+
* @param array<mixed> $json
55+
*
56+
* @return resource
57+
*/
58+
protected function request(string $endpoint, array $json)
59+
{
60+
try {
61+
$response = $this->client->post($endpoint, [
62+
'query' => [
63+
'token' => $this->getApiKey(),
64+
],
65+
'json' => $json,
66+
]);
67+
} catch (\GuzzleHttp\Exception\ClientException $e) {
68+
$message = 'No response';
69+
70+
$response = $e->getResponse();
71+
72+
/**
73+
* You could use $e->hasResponse() but that is not accurate enough,
74+
* as phpstan will be analysing against method signatures from guzzle 6 & 7
75+
*/
76+
if ($response !== null) {
77+
$message = $response->getBody();
78+
79+
$json = json_decode($message);
80+
if (json_last_error() === JSON_ERROR_NONE) {
81+
$messages = [];
82+
foreach ($json as $error) {
83+
$messages[] = $error->message;
84+
}
85+
$message = implode(', ', $messages);
86+
}
87+
}
88+
89+
throw new APIException("Failed to render PDF: {$message}", $e->getCode(), $e);
90+
} catch (\Exception $e) {
91+
throw new APIException("Failed to render PDF: {$e->getMessage()}", $e->getCode(), $e);
92+
}
93+
94+
return StreamWrapper::getResource($response->getBody());
95+
}
96+
97+
/**
98+
* Sets the browserless API key
99+
*
100+
* @param string $apiKey
101+
* @return self
102+
*/
103+
private function setApiKey(string $apiKey): self
104+
{
105+
$this->apiKey = $apiKey;
106+
return $this;
107+
}
108+
}

src/Browserless/Screenshot.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace SynergiTech\ChromePDF\Browserless;
4+
5+
class Screenshot
6+
{
7+
use Client;
8+
9+
/**
10+
* Defaults to the following options:
11+
* - quality: 75
12+
* - type: jpeg
13+
* - fullPage: true
14+
*
15+
* @param array<string, mixed> $options see https://www.browserless.io/docs/screenshot#custom-options
16+
*
17+
* @return resource
18+
*/
19+
public function render(string $url, array $options = [])
20+
{
21+
$options = array_merge([
22+
'quality' => 75,
23+
'type' => 'jpeg',
24+
'fullPage' => false,
25+
], $options);
26+
27+
return $this->request(
28+
endpoint: '/screenshot',
29+
json: [
30+
'url' => $url,
31+
'options' => $options,
32+
]
33+
);
34+
}
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace SynergiTech\ChromePDF\Test\Browserless;
4+
5+
use GuzzleHttp\Psr7\Response;
6+
7+
use SynergiTech\ChromePDF\Browserless;
8+
use SynergiTech\ChromePDF\Browserless\Screenshot;
9+
use SynergiTech\ChromePDF\Chrome;
10+
use SynergiTech\ChromePDF\Test\TestCase;
11+
12+
class ScreenshotTest extends TestCase
13+
{
14+
private function getMockedClient()
15+
{
16+
return $this->getMockBuilder(Chrome::class)
17+
->setMethods(['post'])
18+
->getMock();
19+
}
20+
21+
public function test_render()
22+
{
23+
$client = $this->getMockedClient();
24+
25+
$client->expects($this->once())
26+
->method('post')
27+
->with($this->anything(), $this->hasKeyValue(['json', 'url'], $this->identicalTo('test')))
28+
->willReturn(new Response(200, [], 'screenshot'));
29+
30+
$bl = new Screenshot('', $client);
31+
$stream = $bl->render('test');
32+
33+
$this->assertIsResource($stream);
34+
$this->assertSame('screenshot', fgets($stream));
35+
}
36+
}

test/BrowserlessTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
use SynergiTech\ChromePDF\Chrome;
1616
use SynergiTech\ChromePDF\Test\TestCase;
1717

18-
use PHPUnit\Framework\Constraint\ArraySubset;
19-
2018
class BrowserlessTest extends TestCase
2119
{
2220
private function getMockedClient()

0 commit comments

Comments
 (0)