Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split in Section Processors instead of a very large command #25

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
465 changes: 14 additions & 451 deletions src/Command/GenerateManifestCommand.php

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/Command/SectionProcessor/ActionsSectionProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Routing\RouterInterface;

final readonly class ActionsSectionProcessor implements SectionProcessor
{
public function __construct(
private ?RouterInterface $router = null,
) {
}

public function process(SymfonyStyle $io, array $config, array $manifest): array|int

Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ActionsSectionProcessor::process() has parameter $config with no value type specified in iterable type array.

Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ActionsSectionProcessor::process() has parameter $manifest with no value type specified in iterable type array.

Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ActionsSectionProcessor::process() return type has no value type specified in iterable type array.
{
if ($config['file_handlers'] === []) {
return $manifest;
}
foreach ($manifest['file_handlers'] as $id => $handler) {
if (str_starts_with((string) $handler['action'], '/')) {
continue;
}
if ($this->router === null) {
$io->error('The router is not available. Unable to generate the file handler action URL.');
return Command::FAILURE;
}
$manifest['file_handlers'][$id]['action'] = $this->router->generate(
$handler['action'],
[],
RouterInterface::RELATIVE_PATH
);
}

return $manifest;
}
}
59 changes: 59 additions & 0 deletions src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use function is_int;

final class ApplicationIconsSectionProcessor implements SectionProcessor
{
use IconsSectionProcessorTrait;

public function __construct(

Check failure on line 17 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ApplicationIconsSectionProcessor::__construct() has parameter $dest with no value type specified in iterable type array.
private readonly Filesystem $filesystem,
#[Autowire('%spomky_labs_pwa.dest%')]
private readonly array $dest,
private readonly null|ImageProcessor $imageProcessor = null,
) {
}

public function process(SymfonyStyle $io, array $config, array $manifest): array|int

Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ApplicationIconsSectionProcessor::process() has parameter $config with no value type specified in iterable type array.

Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ApplicationIconsSectionProcessor::process() has parameter $manifest with no value type specified in iterable type array.

Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\SectionProcessor\ApplicationIconsSectionProcessor::process() return type has no value type specified in iterable type array.
{
if ($config['icons'] === []) {
return $manifest;
}
$result = $this->processIcons($io, $config['icons']);
if (is_int($result)) {
return $result;
}
$manifest['icons'] = $result;
$io->info('Icons are built');

return $manifest;
}

protected function getFilesystem(): Filesystem
{
return $this->filesystem;
}

protected function getIconPrefixUrl(): string
{
return $this->dest['icon_prefix_url'];
}

protected function getIconFolder(): string
{
return $this->dest['icon_folder'];
}

protected function getImageProcessor(): ?ImageProcessor
{
return $this->imageProcessor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use SpomkyLabs\PwaBundle\Command\Client;
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use function is_int;

final class ApplicationScreenshotsSectionProcessor implements SectionProcessor
{
use ScreenshotsProcessorTrait;

private readonly null|Client $webClient;

Check failure on line 18 in src/Command/SectionProcessor/ApplicationScreenshotsSectionProcessor.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Property SpomkyLabs\PwaBundle\Command\SectionProcessor\ApplicationScreenshotsSectionProcessor::$webClient has unknown class SpomkyLabs\PwaBundle\Command\Client as its type.

public function __construct(
private readonly Filesystem $filesystem,
#[Autowire('%spomky_labs_pwa.dest%')]
private readonly array $dest,
#[Autowire('@pwa.web_client')]
null|Client $webClient,
private readonly null|ImageProcessor $imageProcessor = null,
) {
if ($webClient === null && class_exists(Client::class)) {
$webClient = Client::createChromeClient();
}
$this->webClient = $webClient;
}

public function process(SymfonyStyle $io, array $config, array $manifest): array|int
{
if ($config['screenshots'] === []) {
return $manifest;
}
$result = $this->processScreenshots($io, $config['screenshots']);
if (is_int($result)) {
return $result;
}
$manifest['screenshots'] = $result;

return $manifest;
}

protected function getFilesystem(): Filesystem
{
return $this->filesystem;
}

protected function getImageProcessor(): ?ImageProcessor
{
return $this->imageProcessor;
}

protected function getWebClient(): ?Client
{
return $this->webClient;
}

protected function getScreenshotPrefixUrl(): string
{
return $this->dest['screenshot_prefix_url'];
}

protected function getScreenshotFolder(): string
{
return $this->dest['screenshot_folder'];
}
}
72 changes: 72 additions & 0 deletions src/Command/SectionProcessor/FileProcessorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use RuntimeException;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Mime\MimeTypes;

trait FileProcessorTrait
{
private null|MimeTypes $mime = null;

abstract protected function getFilesystem(): Filesystem;

protected function getMime(): MimeTypes
{
if (! isset($this->mime)) {
$this->mime = MimeTypes::getDefault();
}
return $this->mime;
}

protected function createDirectoryIfNotExists(string $folder): bool
{
try {
if (! $this->getFilesystem()->exists($folder)) {
$this->getFilesystem()
->mkdir($folder);
}
} catch (IOExceptionInterface) {
return false;
}

return true;
}

/**
* @param array<string|null> $components
* @return array{src: string, type: string}
*/
protected function storeFile(string $data, string $prefixUrl, string $storageFolder, array $components): array
{
$tempFilename = $this->getFilesystem()
->tempnam($storageFolder, 'pwa-');
$hash = mb_substr(hash('sha256', $data), 0, 8);
file_put_contents($tempFilename, $data);
$mime = $this->getMime()
->guessMimeType($tempFilename);
$extension = $this->getMime()
->getExtensions($mime);

if (empty($extension)) {
throw new RuntimeException(sprintf('Unable to guess the extension for the mime type "%s"', $mime));
}

$components[] = $hash;
$filename = sprintf('%s.%s', implode('-', $components), $extension[0]);
$localFilename = sprintf('%s/%s', rtrim($storageFolder, '/'), $filename);

file_put_contents($localFilename, $data);
$this->getFilesystem()
->remove($tempFilename);

return [
'src' => sprintf('%s/%s', $prefixUrl, $filename),
'type' => $mime,
];
}
}
62 changes: 62 additions & 0 deletions src/Command/SectionProcessor/IconsSectionProcessorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use function is_int;

trait IconsSectionProcessorTrait
{
use ImageSectionProcessorTrait;

abstract protected function getIconPrefixUrl(): string;

abstract protected function getIconFolder(): string;

/**
* @param array{src: string, sizes: array<int>, format: ?string, purpose: ?string} $icons
*/
protected function processIcons(SymfonyStyle $io, array $icons): array|int
{
if (! $this->createDirectoryIfNotExists($this->getIconFolder()) || ! $this->checkImageProcessor($io)) {
return Command::FAILURE;
}
$result = [];
foreach ($icons as $icon) {
foreach ($icon['sizes'] as $size) {
if (! is_int($size) || $size < 0) {
$io->error('The icon size must be a positive integer');
return Command::FAILURE;
}
$data = $this->loadFileAndConvert($icon['src'], $size, $icon['format'] ?? null);
if ($data === null) {
$io->error(sprintf('Unable to read the icon "%s"', $icon['src']));
return Command::FAILURE;
}

$iconManifest = $this->storeIcon($data, $size, $icon['purpose'] ?? null);
$result[] = $iconManifest;
}
}

return $result;
}

/**
* @return array{src: string, sizes: string, type: string, purpose: ?string}
*/
private function storeIcon(string $data, int $size, ?string $purpose): array
{
$fileData = $this->storeFile(
$data,
$this->getIconPrefixUrl(),
$this->getIconFolder(),
['icon', $purpose, $size === 0 ? 'any' : $size . 'x' . $size]
);

return $this->handleSizeAndPurpose($purpose, $size, $fileData);
}
}
55 changes: 55 additions & 0 deletions src/Command/SectionProcessor/ImageSectionProcessorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command\SectionProcessor;

use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor;
use Symfony\Component\Console\Style\SymfonyStyle;

trait ImageSectionProcessorTrait
{
use FileProcessorTrait;

abstract protected function getImageProcessor(): ?ImageProcessor;

protected function handleSizeAndPurpose(?string $purpose, int $size, array $fileData): array
{
$sizes = $size === 0 ? 'any' : $size . 'x' . $size;
$fileData += [
'sizes' => $sizes,
];

if ($purpose !== null) {
$fileData += [
'purpose' => $purpose,
];
}

return $fileData;
}

protected function loadFileAndConvert(string $src, ?int $size, ?string $format): ?string
{
$data = file_get_contents($src);
if ($data === false) {
return null;
}
if ($size !== 0 && $size !== null) {
$data = $this->getImageProcessor()
->process($data, $size, $size, $format);
}

return $data;
}

protected function checkImageProcessor(SymfonyStyle $io): bool
{
if ($this->getImageProcessor() === null) {
$io->error('Image processor not found');
return false;
}

return true;
}
}
Loading