Skip to content

Commit 62b08cf

Browse files
committed
feat: add configuration factory and test for Healdless chromium feature
1 parent 412d207 commit 62b08cf

File tree

9 files changed

+237
-39
lines changed

9 files changed

+237
-39
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ build:
77
.PHONY: test
88
test: build
99
$(MAKE) -C src/Bundle test IMAGE_TAG="${IMAGE_TAG}" ARGS="${ARGS}"
10+
11+
.PHONY: phpstan
12+
phpstan:
13+
php vendor/bin/phpstan analyse --level max src/
14+
15+
.PHONY: php-cs-fixer
16+
php-cs-fixer:
17+
tools/php-cs-fixer/vendor/bin/php-cs-fixer fix ./src

src/Backend/HeadlessChromium/ExtraOption/DisableFeatures.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
class DisableFeatures implements ExtraOption
1010
{
11+
/**
12+
* @param array<string> $features
13+
*/
1114
public function __construct(private readonly array $features)
1215
{
1316
}

src/Backend/HeadlessChromium/ExtraOption/PrintToPdf.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
namespace KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption;
66

77
use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption;
8-
use SplFileInfo;
98

109
class PrintToPdf implements ExtraOption
1110
{
12-
public function __construct(private readonly SplFileInfo $file)
11+
public function __construct(private readonly string $filePath)
1312
{
1413
}
1514

@@ -20,11 +19,11 @@ public function isRepeatable(): bool
2019

2120
public function compile(): array
2221
{
23-
return ['--print-to-pdf=' . $this->file];
22+
return ['--print-to-pdf=' . $this->filePath];
2423
}
2524

26-
public function getFile(): SplFileInfo
25+
public function getFilePath(): string
2726
{
28-
return $this->file;
27+
return $this->filePath;
2928
}
3029
}

src/Backend/HeadlessChromium/HeadlessChromiumAdapter.php

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use KNPLabs\Snappy\Core\Backend\Options;
1010
use Psr\Http\Message\StreamFactoryInterface;
1111
use Psr\Http\Message\StreamInterface;
12-
use Psr\Http\Message\UriFactoryInterface;
1312
use Psr\Http\Message\UriInterface;
1413
use Symfony\Component\Process\Process;
1514
use InvalidArgumentException;
@@ -22,17 +21,13 @@ final class HeadlessChromiumAdapter implements UriToPdf
2221
*/
2322
use Reconfigurable;
2423

25-
private string $tempDir;
26-
2724
public function __construct(
2825
private string $binary,
2926
private int $timeout,
3027
HeadlessChromiumFactory $factory,
3128
Options $options,
3229
private readonly StreamFactoryInterface $streamFactory,
33-
private readonly UriFactoryInterface $uriFactory,
3430
) {
35-
$this->tempDir = __DIR__;
3631
self::validateOptions($options);
3732

3833
$this->factory = $factory;
@@ -65,7 +60,7 @@ public function getPrintToPdfFilePath(): string
6560
if (!empty($printToPdfOption)) {
6661
$printToPdfOption = \array_values($printToPdfOption)[0];
6762

68-
return $printToPdfOption->getFile()->getPathname();
63+
return $printToPdfOption->getFilePath();
6964
}
7065

7166
throw new RuntimeException('Missing option print to pdf.');
@@ -89,18 +84,29 @@ private static function validateOptions(Options $options): void
8984
}
9085

9186
/**
92-
* @return array<float|int|string>
87+
* @return array<mixed>
9388
*/
9489
private function compileOptions(): array
9590
{
9691
return \array_reduce(
9792
$this->options->extraOptions,
98-
fn (array $carry, ExtraOption $extraOption) => $this->options->pageOrientation !== null
99-
?: [
100-
...$carry,
101-
...$extraOption->compile(),
102-
],
103-
[],
93+
/**
94+
* @param array<mixed> $carry
95+
* @param ExtraOption $extraOption
96+
*
97+
* @return array<mixed>
98+
*/
99+
function (array $carry, $extraOption) {
100+
if ($extraOption instanceof ExtraOption) {
101+
return [
102+
...$carry,
103+
...$extraOption->compile(),
104+
];
105+
}
106+
107+
return $carry;
108+
},
109+
[]
104110
);
105111
}
106112
}

src/Backend/HeadlessChromium/HeadlessChromiumFactory.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use KNPLabs\Snappy\Core\Backend\Factory;
88
use KNPLabs\Snappy\Core\Backend\Options;
99
use Psr\Http\Message\StreamFactoryInterface;
10-
use Psr\Http\Message\UriFactoryInterface;
1110

1211
/**
1312
* @implements Factory<HeadlessChromiumAdapter>
@@ -18,7 +17,6 @@ public function __construct(
1817
private readonly string $binary,
1918
private readonly int $timeout,
2019
private readonly StreamFactoryInterface $streamFactory,
21-
private readonly UriFactoryInterface $uriFactory,
2220
) {
2321
}
2422

@@ -30,7 +28,6 @@ public function create(Options $options): HeadlessChromiumAdapter
3028
$this,
3129
$options,
3230
$this->streamFactory,
33-
$this->uriFactory,
3431
);
3532
}
3633
}

src/Backend/HeadlessChromium/Tests/HeadlessChromiumAdapterTest.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use PHPUnit\Framework\TestCase;
1414
use Psr\Http\Message\StreamFactoryInterface;
1515
use Psr\Http\Message\StreamInterface;
16-
use Psr\Http\Message\UriFactoryInterface;
1716
use Psr\Http\Message\UriInterface;
1817
use SplFileInfo;
1918
use RuntimeException;
@@ -30,22 +29,18 @@ final class HeadlessChromiumAdapterTest extends TestCase
3029

3130
private string $directory;
3231

33-
private UriFactoryInterface $uriFactory;
34-
3532
private SplFileInfo $outputFile;
3633

3734
protected function setUp(): void
3835
{
39-
$this->uriFactory = $this->createMock(UriFactoryInterface::class);
4036
$this->directory = __DIR__;
4137
$this->outputFile = new SplFileInfo($this->directory . '/file.pdf');
42-
$this->options = new Options(null, [new Headless(), new PrintToPdf($this->outputFile), new DisableGpu()]);
38+
$this->options = new Options(null, [new Headless(), new PrintToPdf($this->outputFile->getPathname()), new DisableGpu()]);
4339
$this->streamFactory = $this->createMock(StreamFactoryInterface::class);
4440
$this->factory = new HeadlessChromiumFactory(
4541
'chromium',
4642
120,
4743
$this->streamFactory,
48-
$this->uriFactory
4944
);
5045
$this->adapter = $this->factory->create($this->options);
5146
}
@@ -55,12 +50,6 @@ public function testGenerateFromUri(): void
5550
$url = $this->createMock(UriInterface::class);
5651
$url->method('__toString')->willReturn('https://google.com');
5752

58-
$this->streamFactory->expects($this->once())
59-
->method('createStream')
60-
->with($this->stringContains($this->outputFile->getPathname()))
61-
->willReturn($this->createMock(StreamInterface::class))
62-
;
63-
6453
$resultStream = $this->adapter->generateFromUri($url);
6554

6655
$this->assertNotNull($resultStream);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration;
6+
7+
use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption\PrintToPdf;
8+
use KNPLabs\Snappy\Backend\HeadlessChromium\HeadlessChromiumAdapter;
9+
use KNPLabs\Snappy\Backend\HeadlessChromium\HeadlessChromiumFactory;
10+
use Psr\Http\Message\StreamFactoryInterface;
11+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
12+
use Symfony\Component\DependencyInjection\ContainerBuilder;
13+
use Symfony\Component\DependencyInjection\Definition;
14+
15+
final class HeadlessChromiumConfigurationFactory implements BackendConfigurationFactory
16+
{
17+
public function getKey(): string
18+
{
19+
return 'chromium';
20+
}
21+
22+
public function isAvailable(): bool
23+
{
24+
return \class_exists(HeadlessChromiumAdapter::class);
25+
}
26+
27+
public function create(
28+
ContainerBuilder $container,
29+
array $configuration,
30+
string $backendId,
31+
string $backendName,
32+
string $factoryId,
33+
Definition $options
34+
): void {
35+
$container
36+
->setDefinition(
37+
$factoryId,
38+
new Definition(
39+
HeadlessChromiumFactory::class,
40+
[
41+
'$streamFactory' => $container->getDefinition(StreamFactoryInterface::class),
42+
'$binary' => $configuration['binary'],
43+
'$timeout' => $configuration['timeout'],
44+
]
45+
)
46+
)
47+
;
48+
49+
$container
50+
->setDefinition(
51+
$backendId,
52+
(new Definition(HeadlessChromiumAdapter::class))
53+
->setFactory([$container->getDefinition($factoryId), 'create'])
54+
->setArgument('$options', $options)
55+
)
56+
;
57+
58+
$container->registerAliasForArgument($backendId, HeadlessChromiumAdapter::class, $backendName);
59+
}
60+
61+
public function getExample(): array
62+
{
63+
return [
64+
'extraOptions' => [
65+
'construct' => [],
66+
'output' => [],
67+
],
68+
];
69+
}
70+
71+
public function addConfiguration(ArrayNodeDefinition $node): void
72+
{
73+
$node
74+
->children()
75+
->scalarNode('binary')
76+
->defaultValue('chromium')
77+
->info('Path or command to run Chromium')
78+
;
79+
80+
$node
81+
->children()
82+
->scalarNode('headless')
83+
->defaultValue('--headless')
84+
->info('The flag to run Chromium in headless mode')
85+
;
86+
87+
$node
88+
->children()
89+
->integerNode('timeout')
90+
->defaultValue(60)
91+
->info('Timeout for Chromium process')
92+
;
93+
94+
$optionsNode = $node
95+
->children()
96+
->arrayNode('options')
97+
->info('Options to configure the Chromium process.')
98+
->addDefaultsIfNotSet()
99+
->children()
100+
;
101+
102+
$optionsNode
103+
->arrayNode('extraOptions')
104+
->info('Extra options passed to the HeadlessChromiumAdapter.')
105+
->children()
106+
->scalarNode('printToPdf')
107+
->info(\sprintf('Configuration passed to %s::__construct().', PrintToPdf::class))
108+
;
109+
}
110+
}

src/Framework/Symfony/DependencyInjection/SnappyExtension.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace KNPLabs\Snappy\Framework\Symfony\DependencyInjection;
66

7+
use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption\PrintToPdf;
78
use KNPLabs\Snappy\Core\Backend\Options;
89
use KNPLabs\Snappy\Core\Backend\Options\PageOrientation;
910
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\BackendConfigurationFactory;
1011
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\DompdfConfigurationFactory;
12+
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\HeadlessChromiumConfigurationFactory;
1113
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\WkHtmlToPdfConfigurationFactory;
1214
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1315
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -67,6 +69,7 @@ private function getFactories(): array
6769
[
6870
new DompdfConfigurationFactory(),
6971
new WkHtmlToPdfConfigurationFactory(),
72+
new HeadlessChromiumConfigurationFactory(),
7073
],
7174
static fn (BackendConfigurationFactory $factory): bool => $factory->isAvailable(),
7275
);
@@ -99,19 +102,34 @@ private function buildOptions(string $backendName, string $backendType, array $c
99102
];
100103

101104
if (isset($configuration['pageOrientation'])) {
102-
if (false === \is_string($configuration['pageOrientation'])) {
103-
throw new InvalidConfigurationException(\sprintf('Invalid “%s” type for “snappy.backends.%s.%s.options.pageOrientation”. The expected type is “string”.', $backendName, $backendType, \gettype($configuration['pageOrientation'])), );
105+
if (!\is_string($configuration['pageOrientation'])) {
106+
throw new InvalidConfigurationException(\sprintf('Invalid type for “snappy.backends.%s.%s.options.pageOrientation”. Expected "string", got "%s".', $backendName, $backendType, \gettype($configuration['pageOrientation'])));
104107
}
105-
106108
$arguments['$pageOrientation'] = PageOrientation::from($configuration['pageOrientation']);
107109
}
108110

109111
if (isset($configuration['extraOptions'])) {
110-
if (false === \is_array($configuration['extraOptions'])) {
111-
throw new InvalidConfigurationException(\sprintf('Invalid “%s” type for “snappy.backends.%s.%s.options.extraOptions”. The expected type is “array”.', $backendName, $backendType, \gettype($configuration['extraOptions'])), );
112+
if (!\is_array($configuration['extraOptions'])) {
113+
throw new InvalidConfigurationException(\sprintf('Invalid type for “snappy.backends.%s.%s.options.extraOptions”. Expected "array", got "%s".', $backendName, $backendType, \gettype($configuration['extraOptions'])));
112114
}
113115

114-
$arguments['$extraOptions'] = $configuration['extraOptions'];
116+
foreach ($configuration['extraOptions'] as $key => $value) {
117+
switch ($key) {
118+
case 'printToPdf':
119+
if (\is_string($value)) {
120+
$arguments['$extraOptions'][] = new PrintToPdf($value);
121+
} else {
122+
throw new InvalidConfigurationException(\sprintf('Invalid type for “snappy.backends.%s.%s.options.extraOptions.printToPdf”. Expected "string", got "%s".', $backendName, $backendType, \gettype($value)));
123+
}
124+
125+
break;
126+
127+
default:
128+
$arguments['$extraOptions'][$key] = $value;
129+
130+
break;
131+
}
132+
}
115133
}
116134

117135
return new Definition(Options::class, $arguments);

0 commit comments

Comments
 (0)