mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-23 13:23:33 +03:00
Improved PreviewGenerator by composing an ImageBuilder that creates new Image objects fore each URL
This commit is contained in:
parent
15247e832e
commit
2c91ded514
12 changed files with 181 additions and 20 deletions
|
@ -2,7 +2,6 @@
|
|||
return [
|
||||
|
||||
'phpwkhtmltopdf' => [
|
||||
'files_location' => 'data/cache',
|
||||
'images' => [
|
||||
'binary' => 'bin/wkhtmltoimage',
|
||||
'type' => 'jpg',
|
||||
|
|
8
config/autoload/preview-generation.global.php
Normal file
8
config/autoload/preview-generation.global.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
return [
|
||||
|
||||
'preview_generation' => [
|
||||
'files_location' => 'data/cache',
|
||||
],
|
||||
|
||||
];
|
|
@ -10,6 +10,7 @@ return [
|
|||
Command\Shortcode\ResolveUrlCommand::class,
|
||||
Command\Shortcode\ListShortcodesCommand::class,
|
||||
Command\Shortcode\GetVisitsCommand::class,
|
||||
Command\Shortcode\GeneratePreviewCommand::class,
|
||||
Command\Visit\ProcessVisitsCommand::class,
|
||||
Command\Config\GenerateCharsetCommand::class,
|
||||
Command\Config\GenerateSecretCommand::class,
|
||||
|
|
|
@ -14,6 +14,7 @@ return [
|
|||
Command\Shortcode\ResolveUrlCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\ListShortcodesCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\GetVisitsCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\GeneratePreviewCommand::class => AnnotatedFactory::class,
|
||||
Command\Visit\ProcessVisitsCommand::class => AnnotatedFactory::class,
|
||||
Command\Config\GenerateCharsetCommand::class => AnnotatedFactory::class,
|
||||
Command\Config\GenerateSecretCommand::class => AnnotatedFactory::class,
|
||||
|
|
87
module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php
Normal file
87
module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GeneratePreviewCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var PreviewGeneratorInterface
|
||||
*/
|
||||
private $previewGenerator;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
private $shortUrlService;
|
||||
|
||||
/**
|
||||
* GeneratePreviewCommand constructor.
|
||||
* @param ShortUrlServiceInterface $shortUrlService
|
||||
* @param PreviewGeneratorInterface $previewGenerator
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({ShortUrlService::class, PreviewGenerator::class, "translator"})
|
||||
*/
|
||||
public function __construct(
|
||||
ShortUrlServiceInterface $shortUrlService,
|
||||
PreviewGeneratorInterface $previewGenerator,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->previewGenerator = $previewGenerator;
|
||||
$this->translator = $translator;
|
||||
$this->shortUrlService = $shortUrlService;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:process-previews')
|
||||
->setDescription(
|
||||
$this->translator->translate(
|
||||
'Processes and generates the previews for every URL, improving performance for later web requests.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$page = 1;
|
||||
do {
|
||||
$shortUrls = $this->shortUrlService->listShortUrls($page);
|
||||
$page += 1;
|
||||
|
||||
foreach ($shortUrls as $shortUrl) {
|
||||
$this->processUrl($shortUrl->getOriginalUrl(), $output);
|
||||
}
|
||||
} while ($page <= $shortUrls->count());
|
||||
|
||||
$output->writeln('<info>' . $this->translator->translate('Finished processing all URLs') . '</info>');
|
||||
}
|
||||
|
||||
protected function processUrl($url, OutputInterface $output)
|
||||
{
|
||||
try {
|
||||
$output->write(sprintf($this->translator->translate('Processing URL %s...'), $url));
|
||||
$this->previewGenerator->generatePreview($url);
|
||||
$output->writeln($this->translator->translate(' <info>Success!</info>'));
|
||||
} catch (PreviewGenerationException $e) {
|
||||
$output->writeln([
|
||||
' <error>' . $this->translator->translate('Error') . '</error>',
|
||||
'<error>' . $e->__toString() . '</error>',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,13 @@
|
|||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Factory\CacheFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\EntityManagerFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\LoggerFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\TranslatorFactory;
|
||||
use Shlinkio\Shlink\Common\Image\ImageFactory;
|
||||
use Shlinkio\Shlink\Common\Image;
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Shlinkio\Shlink\Common\Service;
|
||||
use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension;
|
||||
|
@ -24,12 +23,13 @@ return [
|
|||
GuzzleHttp\Client::class => InvokableFactory::class,
|
||||
Cache::class => CacheFactory::class,
|
||||
'Logger_Shlink' => LoggerFactory::class,
|
||||
Image::class => ImageFactory::class,
|
||||
|
||||
Translator::class => TranslatorFactory::class,
|
||||
TranslatorExtension::class => AnnotatedFactory::class,
|
||||
LocaleMiddleware::class => AnnotatedFactory::class,
|
||||
|
||||
Image\ImageBuilder::class => Image\ImageBuilderFactory::class,
|
||||
|
||||
Service\IpLocationResolver::class => AnnotatedFactory::class,
|
||||
Service\PreviewGenerator::class => AnnotatedFactory::class,
|
||||
],
|
||||
|
|
10
module/Common/src/Image/ImageBuilder.php
Normal file
10
module/Common/src/Image/ImageBuilder.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Image;
|
||||
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
|
||||
class ImageBuilder extends AbstractPluginManager implements ImageBuilderInterface
|
||||
{
|
||||
protected $instanceOf = Image::class;
|
||||
}
|
31
module/Common/src/Image/ImageBuilderFactory.php
Normal file
31
module/Common/src/Image/ImageBuilderFactory.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Image;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class ImageBuilderFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new ImageBuilder($container, ['factories' => [
|
||||
Image::class => ImageFactory::class,
|
||||
]]);
|
||||
}
|
||||
}
|
8
module/Common/src/Image/ImageBuilderInterface.php
Normal file
8
module/Common/src/Image/ImageBuilderInterface.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Image;
|
||||
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
interface ImageBuilderInterface extends ServiceLocatorInterface
|
||||
{
|
||||
}
|
|
@ -25,6 +25,12 @@ class ImageFactory implements FactoryInterface
|
|||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
$config = $container->get('config')['phpwkhtmltopdf'];
|
||||
return new Image(isset($config['images']) ? $config['images'] : null);
|
||||
$image = new Image(isset($config['images']) ? $config['images'] : null);
|
||||
|
||||
if (isset($options['url'])) {
|
||||
$image->setPage($options['url']);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,11 @@ use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
|||
use Doctrine\Common\Cache\Cache;
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilder;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilderInterface;
|
||||
|
||||
class PreviewGenerator implements PreviewGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @var Image
|
||||
*/
|
||||
private $image;
|
||||
/**
|
||||
* @var Cache
|
||||
*/
|
||||
|
@ -20,20 +18,24 @@ class PreviewGenerator implements PreviewGeneratorInterface
|
|||
* @var string
|
||||
*/
|
||||
private $location;
|
||||
/**
|
||||
* @var ImageBuilderInterface
|
||||
*/
|
||||
private $imageBuilder;
|
||||
|
||||
/**
|
||||
* PreviewGenerator constructor.
|
||||
* @param Image $image
|
||||
* @param ImageBuilderInterface $imageBuilder
|
||||
* @param Cache $cache
|
||||
* @param string $location
|
||||
*
|
||||
* @Inject({Image::class, Cache::class, "config.phpwkhtmltopdf.files_location"})
|
||||
* @Inject({ImageBuilder::class, Cache::class, "config.preview_generation.files_location"})
|
||||
*/
|
||||
public function __construct(Image $image, Cache $cache, $location)
|
||||
public function __construct(ImageBuilderInterface $imageBuilder, Cache $cache, $location)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->cache = $cache;
|
||||
$this->location = $location;
|
||||
$this->imageBuilder = $imageBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,17 +47,19 @@ class PreviewGenerator implements PreviewGeneratorInterface
|
|||
*/
|
||||
public function generatePreview($url)
|
||||
{
|
||||
$cacheId = sprintf('preview_%s.%s', urlencode($url), $this->image->type);
|
||||
/** @var Image $image */
|
||||
$image = $this->imageBuilder->build(Image::class, ['url' => $url]);
|
||||
|
||||
$cacheId = sprintf('preview_%s.%s', urlencode($url), $image->type);
|
||||
if ($this->cache->contains($cacheId)) {
|
||||
return $this->cache->fetch($cacheId);
|
||||
}
|
||||
|
||||
$path = $this->location . '/' . $cacheId;
|
||||
$this->image->setPage($url);
|
||||
$this->image->saveAs($path);
|
||||
$image->saveAs($path);
|
||||
|
||||
// Check if an error occurred
|
||||
$error = $this->image->getError();
|
||||
$error = $image->getError();
|
||||
if (! empty($error)) {
|
||||
throw PreviewGenerationException::fromImageError($error);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ use mikehaertl\wkhtmlto\Image;
|
|||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Image\ImageBuilder;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGenerator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class PreviewGeneratorTest extends TestCase
|
||||
{
|
||||
|
@ -27,7 +29,13 @@ class PreviewGeneratorTest extends TestCase
|
|||
{
|
||||
$this->image = $this->prophesize(Image::class);
|
||||
$this->cache = new ArrayCache();
|
||||
$this->generator = new PreviewGenerator($this->image->reveal(), $this->cache, 'dir');
|
||||
$this->generator = new PreviewGenerator(new ImageBuilder(new ServiceManager(), [
|
||||
'factories' => [
|
||||
Image::class => function () {
|
||||
return $this->image->reveal();
|
||||
},
|
||||
]
|
||||
]), $this->cache, 'dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +58,6 @@ class PreviewGeneratorTest extends TestCase
|
|||
$cacheId = sprintf('preview_%s.png', urlencode($url));
|
||||
$expectedPath = 'dir/' . $cacheId;
|
||||
|
||||
$this->image->setPage($url)->shouldBeCalledTimes(1);
|
||||
$this->image->saveAs($expectedPath)->shouldBeCalledTimes(1);
|
||||
$this->image->getError()->willReturn('')->shouldBeCalledTimes(1);
|
||||
|
||||
|
@ -69,7 +76,6 @@ class PreviewGeneratorTest extends TestCase
|
|||
$cacheId = sprintf('preview_%s.png', urlencode($url));
|
||||
$expectedPath = 'dir/' . $cacheId;
|
||||
|
||||
$this->image->setPage($url)->shouldBeCalledTimes(1);
|
||||
$this->image->saveAs($expectedPath)->shouldBeCalledTimes(1);
|
||||
$this->image->getError()->willReturn('Error!!')->shouldBeCalledTimes(1);
|
||||
|
||||
|
|
Loading…
Reference in a new issue