Skip to content

Commit

Permalink
feat: add configuration factory and test for Healdless chromium feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthurlbc committed Nov 8, 2024
1 parent 412d207 commit 83f946b
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 39 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ build:
.PHONY: test
test: build
$(MAKE) -C src/Bundle test IMAGE_TAG="${IMAGE_TAG}" ARGS="${ARGS}"

.PHONY: phpstan
phpstan:
php vendor/bin/phpstan analyse --level max src/

.PHONY: php-cs-fixer
php-cs-fixer:
tools/php-cs-fixer/vendor/bin/php-cs-fixer fix ./src
3 changes: 3 additions & 0 deletions src/Backend/HeadlessChromium/ExtraOption/DisableFeatures.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

class DisableFeatures implements ExtraOption
{
/**
* @param array<string> $features
*/
public function __construct(private readonly array $features)
{
}
Expand Down
9 changes: 4 additions & 5 deletions src/Backend/HeadlessChromium/ExtraOption/PrintToPdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
namespace KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption;

use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption;
use SplFileInfo;

class PrintToPdf implements ExtraOption
{
public function __construct(private readonly SplFileInfo $file)
public function __construct(private readonly string $filePath)
{
}

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

public function compile(): array
{
return ['--print-to-pdf=' . $this->file];
return ['--print-to-pdf=' . $this->filePath];
}

public function getFile(): SplFileInfo
public function getFilePath(): string
{
return $this->file;
return $this->filePath;
}
}
32 changes: 19 additions & 13 deletions src/Backend/HeadlessChromium/HeadlessChromiumAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use KNPLabs\Snappy\Core\Backend\Options;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\Process\Process;
use InvalidArgumentException;
Expand All @@ -22,17 +21,13 @@ final class HeadlessChromiumAdapter implements UriToPdf
*/
use Reconfigurable;

private string $tempDir;

public function __construct(
private string $binary,
private int $timeout,
HeadlessChromiumFactory $factory,
Options $options,
private readonly StreamFactoryInterface $streamFactory,
private readonly UriFactoryInterface $uriFactory,
) {
$this->tempDir = __DIR__;
self::validateOptions($options);

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

return $printToPdfOption->getFile()->getPathname();
return $printToPdfOption->getFilePath();
}

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

/**
* @return array<float|int|string>
* @return array<mixed>
*/
private function compileOptions(): array
{
return \array_reduce(
$this->options->extraOptions,
fn (array $carry, ExtraOption $extraOption) => $this->options->pageOrientation !== null
?: [
...$carry,
...$extraOption->compile(),
],
[],
/**
* @param array<mixed> $carry
* @param ExtraOption $extraOption
*
* @return array<mixed>
*/
function (array $carry, $extraOption) {
if ($extraOption instanceof ExtraOption) {
return [
...$carry,
...$extraOption->compile(),
];
}

return $carry;
},
[]
);
}
}
3 changes: 0 additions & 3 deletions src/Backend/HeadlessChromium/HeadlessChromiumFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use KNPLabs\Snappy\Core\Backend\Factory;
use KNPLabs\Snappy\Core\Backend\Options;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;

/**
* @implements Factory<HeadlessChromiumAdapter>
Expand All @@ -18,7 +17,6 @@ public function __construct(
private readonly string $binary,
private readonly int $timeout,
private readonly StreamFactoryInterface $streamFactory,
private readonly UriFactoryInterface $uriFactory,
) {
}

Expand All @@ -30,7 +28,6 @@ public function create(Options $options): HeadlessChromiumAdapter
$this,
$options,
$this->streamFactory,
$this->uriFactory,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use SplFileInfo;
use RuntimeException;
Expand All @@ -30,22 +29,18 @@ final class HeadlessChromiumAdapterTest extends TestCase

private string $directory;

private UriFactoryInterface $uriFactory;

private SplFileInfo $outputFile;

protected function setUp(): void
{
$this->uriFactory = $this->createMock(UriFactoryInterface::class);
$this->directory = __DIR__;
$this->outputFile = new SplFileInfo($this->directory . '/file.pdf');
$this->options = new Options(null, [new Headless(), new PrintToPdf($this->outputFile), new DisableGpu()]);
$this->options = new Options(null, [new Headless(), new PrintToPdf($this->outputFile->getPathname()), new DisableGpu()]);
$this->streamFactory = $this->createMock(StreamFactoryInterface::class);
$this->factory = new HeadlessChromiumFactory(
'chromium',
120,
$this->streamFactory,
$this->uriFactory
);
$this->adapter = $this->factory->create($this->options);
}
Expand All @@ -55,12 +50,6 @@ public function testGenerateFromUri(): void
$url = $this->createMock(UriInterface::class);
$url->method('__toString')->willReturn('https://google.com');

$this->streamFactory->expects($this->once())
->method('createStream')
->with($this->stringContains($this->outputFile->getPathname()))
->willReturn($this->createMock(StreamInterface::class))
;

$resultStream = $this->adapter->generateFromUri($url);

$this->assertNotNull($resultStream);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types = 1);

namespace KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration;

use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption\PrintToPdf;
use KNPLabs\Snappy\Backend\HeadlessChromium\HeadlessChromiumAdapter;
use KNPLabs\Snappy\Backend\HeadlessChromium\HeadlessChromiumFactory;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

final class HeadlessChromiumConfigurationFactory implements BackendConfigurationFactory
{
public function getKey(): string
{
return 'chromium';
}

public function isAvailable(): bool
{
return \class_exists(HeadlessChromiumAdapter::class);
}

public function create(
ContainerBuilder $container,
array $configuration,
string $backendId,
string $backendName,
string $factoryId,
Definition $options
): void {
$container
->setDefinition(
$factoryId,
new Definition(
HeadlessChromiumFactory::class,
[
'$streamFactory' => $container->getDefinition(Psr17Factory::class),
'$binary' => $configuration['binary'],
'$timeout' => $configuration['timeout'],
]
)
)
;

$container
->setDefinition(
$backendId,
(new Definition(HeadlessChromiumAdapter::class))
->setFactory([$container->getDefinition($factoryId), 'create'])
->setArgument('$options', $options)
)
;

$container->registerAliasForArgument($backendId, HeadlessChromiumAdapter::class, $backendName);
}

public function getExample(): array
{
return [
'extraOptions' => [
'construct' => [],
'output' => [],
],
];
}

public function addConfiguration(ArrayNodeDefinition $node): void
{
$node
->children()
->scalarNode('binary')
->defaultValue('chromium')
->info('Path or command to run Chromium')
;

$node
->children()
->scalarNode('headless')
->defaultValue('--headless')
->info('The flag to run Chromium in headless mode')
;

$node
->children()
->integerNode('timeout')
->defaultValue(60)
->info('Timeout for Chromium process')
;

$optionsNode = $node
->children()
->arrayNode('options')
->info('Options to configure the Chromium process.')
->addDefaultsIfNotSet()
->children()
;

$optionsNode
->arrayNode('extraOptions')
->info('Extra options passed to the HeadlessChromiumAdapter.')
->children()
->scalarNode('printToPdf')
->info(\sprintf('Configuration passed to %s::__construct().', PrintToPdf::class))
;
}
}
30 changes: 24 additions & 6 deletions src/Framework/Symfony/DependencyInjection/SnappyExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace KNPLabs\Snappy\Framework\Symfony\DependencyInjection;

use KNPLabs\Snappy\Backend\HeadlessChromium\ExtraOption\PrintToPdf;
use KNPLabs\Snappy\Core\Backend\Options;
use KNPLabs\Snappy\Core\Backend\Options\PageOrientation;
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\BackendConfigurationFactory;
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\DompdfConfigurationFactory;
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\HeadlessChromiumConfigurationFactory;
use KNPLabs\Snappy\Framework\Symfony\DependencyInjection\Configuration\WkHtmlToPdfConfigurationFactory;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -67,6 +69,7 @@ private function getFactories(): array
[
new DompdfConfigurationFactory(),
new WkHtmlToPdfConfigurationFactory(),
new HeadlessChromiumConfigurationFactory(),
],
static fn (BackendConfigurationFactory $factory): bool => $factory->isAvailable(),
);
Expand Down Expand Up @@ -99,19 +102,34 @@ private function buildOptions(string $backendName, string $backendType, array $c
];

if (isset($configuration['pageOrientation'])) {
if (false === \is_string($configuration['pageOrientation'])) {
throw new InvalidConfigurationException(\sprintf('Invalid “%s” type for “snappy.backends.%s.%s.options.pageOrientation”. The expected type is “string”.', $backendName, $backendType, \gettype($configuration['pageOrientation'])), );
if (!\is_string($configuration['pageOrientation'])) {
throw new InvalidConfigurationException(\sprintf('Invalid type for “snappy.backends.%s.%s.options.pageOrientation”. Expected "string", got "%s".', $backendName, $backendType, \gettype($configuration['pageOrientation'])));
}

$arguments['$pageOrientation'] = PageOrientation::from($configuration['pageOrientation']);
}

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

$arguments['$extraOptions'] = $configuration['extraOptions'];
foreach ($configuration['extraOptions'] as $key => $value) {
switch ($key) {
case 'printToPdf':
if (\is_string($value)) {
$arguments['$extraOptions'][] = new PrintToPdf($value);
} else {
throw new InvalidConfigurationException(\sprintf('Invalid type for “snappy.backends.%s.%s.options.extraOptions.printToPdf”. Expected "string", got "%s".', $backendName, $backendType, \gettype($value)));
}

break;

default:
$arguments['$extraOptions'][$key] = $value;

break;
}
}
}

return new Definition(Options::class, $arguments);
Expand Down
Loading

0 comments on commit 83f946b

Please sign in to comment.