Created PreviewGenerator module

This commit is contained in:
Alejandro Celaya
2019-08-11 19:38:46 +02:00
parent 1fd677df5a
commit 47ea4218d0
21 changed files with 95 additions and 32 deletions

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator;
use Symfony\Component\Filesystem\Filesystem;
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
return [
'dependencies' => [
'factories' => [
Image\ImageBuilder::class => Image\ImageBuilderFactory::class,
Service\PreviewGenerator::class => ConfigAbstractFactory::class,
],
],
ConfigAbstractFactory::class => [
Service\PreviewGenerator::class => [
Image\ImageBuilder::class,
Filesystem::class,
'config.preview_generation.files_location',
],
],
];

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator;
use function Shlinkio\Shlink\Common\loadConfigFromGlob;
/** @deprecated */
class ConfigProvider
{
public function __invoke(): array
{
return loadConfigFromGlob(__DIR__ . '/../config/{,*.}config.php');
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\Image;
use mikehaertl\wkhtmlto\Image;
use Zend\ServiceManager\AbstractPluginManager;
/** @deprecated */
class ImageBuilder extends AbstractPluginManager implements ImageBuilderInterface
{
protected $instanceOf = Image::class;
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\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;
/** @deprecated */
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,
]]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\Image;
use Zend\ServiceManager\ServiceLocatorInterface;
/** @deprecated */
interface ImageBuilderInterface extends ServiceLocatorInterface
{
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\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;
/** @deprecated */
class ImageFactory 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)
{
$config = $container->get('config')['wkhtmltopdf'];
$image = new Image($config['images'] ?? null);
if ($options['url'] ?? null) {
$image->setPage($options['url']);
}
return $image;
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\Service;
use mikehaertl\wkhtmlto\Image;
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
use Shlinkio\Shlink\PreviewGenerator\Image\ImageBuilderInterface;
use Symfony\Component\Filesystem\Filesystem;
use function sprintf;
use function urlencode;
/** @deprecated */
class PreviewGenerator implements PreviewGeneratorInterface
{
/** @var string */
private $location;
/** @var ImageBuilderInterface */
private $imageBuilder;
/** @var Filesystem */
private $filesystem;
public function __construct(ImageBuilderInterface $imageBuilder, Filesystem $filesystem, string $location)
{
$this->location = $location;
$this->imageBuilder = $imageBuilder;
$this->filesystem = $filesystem;
}
/**
* Generates and stores preview for provided website and returns the path to the image file
*
* @throws PreviewGenerationException
*/
public function generatePreview(string $url): string
{
$image = $this->imageBuilder->build(Image::class, ['url' => $url]);
// If the file already exists, return its path
$cacheId = sprintf('preview_%s.%s', urlencode($url), $image->type);
$path = $this->location . '/' . $cacheId;
if ($this->filesystem->exists($path)) {
return $path;
}
// Save and check if an error occurred
$image->saveAs($path);
$error = $image->getError();
if (! empty($error)) {
throw PreviewGenerationException::fromImageError($error);
}
// Cache the path and return it
return $path;
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\PreviewGenerator\Service;
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
/** @deprecated */
interface PreviewGeneratorInterface
{
/**
* Generates and stores preview for provided website and returns the path to the image file
*
* @throws PreviewGenerationException
*/
public function generatePreview(string $url): string;
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\PreviewGenerator;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\PreviewGenerator\ConfigProvider;
class ConfigProviderTest extends TestCase
{
/** @var ConfigProvider */
private $configProvider;
public function setUp(): void
{
$this->configProvider = new ConfigProvider();
}
/** @test */
public function configIsReturned(): void
{
$config = ($this->configProvider)();
$this->assertArrayHasKey('dependencies', $config);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\PreviewGenerator\Image;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\PreviewGenerator\Image\ImageBuilder;
use Shlinkio\Shlink\PreviewGenerator\Image\ImageBuilderFactory;
use Zend\ServiceManager\ServiceManager;
class ImageBuilderFactoryTest extends TestCase
{
/** @var ImageBuilderFactory */
private $factory;
public function setUp(): void
{
$this->factory = new ImageBuilderFactory();
}
/** @test */
public function serviceIsCreated()
{
$instance = $this->factory->__invoke(new ServiceManager(), '');
$this->assertInstanceOf(ImageBuilder::class, $instance);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\PreviewGenerator\Image;
use mikehaertl\wkhtmlto\Image;
use PHPUnit\Framework\TestCase;
use ReflectionObject;
use Shlinkio\Shlink\PreviewGenerator\Image\ImageFactory;
use Zend\ServiceManager\ServiceManager;
class ImageFactoryTest extends TestCase
{
/** @var ImageFactory */
private $factory;
public function setUp(): void
{
$this->factory = new ImageFactory();
}
/** @test */
public function noPageIsSetWhenOptionsAreNotProvided()
{
/** @var Image $image */
$image = $this->factory->__invoke(new ServiceManager(['services' => [
'config' => ['wkhtmltopdf' => []],
]]), '');
$this->assertInstanceOf(Image::class, $image);
$ref = new ReflectionObject($image);
$page = $ref->getProperty('_page');
$page->setAccessible(true);
$this->assertNull($page->getValue($image));
}
/** @test */
public function aPageIsSetWhenOptionsIncludeTheUrl()
{
$expectedPage = 'foo/bar.html';
/** @var Image $image */
$image = $this->factory->__invoke(new ServiceManager(['services' => [
'config' => ['wkhtmltopdf' => []],
]]), '', ['url' => $expectedPage]);
$this->assertInstanceOf(Image::class, $image);
$ref = new ReflectionObject($image);
$page = $ref->getProperty('_page');
$page->setAccessible(true);
$this->assertEquals($expectedPage, $page->getValue($image));
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\PreviewGenerator\Service;
use mikehaertl\wkhtmlto\Image;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
use Shlinkio\Shlink\PreviewGenerator\Image\ImageBuilder;
use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator;
use Symfony\Component\Filesystem\Filesystem;
use Zend\ServiceManager\ServiceManager;
use function sprintf;
use function urlencode;
class PreviewGeneratorTest extends TestCase
{
/** @var PreviewGenerator */
private $generator;
/** @var ObjectProphecy */
private $image;
/** @var ObjectProphecy */
private $filesystem;
public function setUp(): void
{
$this->image = $this->prophesize(Image::class);
$this->filesystem = $this->prophesize(Filesystem::class);
$this->generator = new PreviewGenerator(new ImageBuilder(new ServiceManager(), [
'factories' => [
Image::class => function () {
return $this->image->reveal();
},
],
]), $this->filesystem->reveal(), 'dir');
}
/** @test */
public function alreadyProcessedElementsAreNotProcessed()
{
$url = 'http://foo.com';
$this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(true)
->shouldBeCalledOnce();
$this->image->saveAs(Argument::cetera())->shouldBeCalledTimes(0);
$this->assertEquals(sprintf('dir/preview_%s.png', urlencode($url)), $this->generator->generatePreview($url));
}
/** @test */
public function nonProcessedElementsAreProcessed()
{
$url = 'http://foo.com';
$cacheId = sprintf('preview_%s.png', urlencode($url));
$expectedPath = 'dir/' . $cacheId;
$this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false)
->shouldBeCalledOnce();
$this->image->saveAs($expectedPath)->shouldBeCalledOnce();
$this->image->getError()->willReturn('')->shouldBeCalledOnce();
$this->assertEquals($expectedPath, $this->generator->generatePreview($url));
}
/** @test */
public function errorWhileGeneratingPreviewThrowsException()
{
$url = 'http://foo.com';
$cacheId = sprintf('preview_%s.png', urlencode($url));
$expectedPath = 'dir/' . $cacheId;
$this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false)
->shouldBeCalledOnce();
$this->image->saveAs($expectedPath)->shouldBeCalledOnce();
$this->image->getError()->willReturn('Error!!')->shouldBeCalledOnce();
$this->expectException(PreviewGenerationException::class);
$this->generator->generatePreview($url);
}
}