Skip to content

Commit 83f946b

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

File tree

9 files changed

+238
-39
lines changed

9 files changed

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

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)