diff --git a/DependencyInjection/Factory/Resolver/AbstractWebPathResolverFactory.php b/DependencyInjection/Factory/Resolver/AbstractWebPathResolverFactory.php new file mode 100644 index 000000000..fdab91c56 --- /dev/null +++ b/DependencyInjection/Factory/Resolver/AbstractWebPathResolverFactory.php @@ -0,0 +1,67 @@ +getChildResolverDefinition(); + $pathResolverDefinition = new ChildDefinition('liip_imagine.util.resolver.prototype.path'); + $pathResolverDefinition->replaceArgument(0, $config['web_root']); + $pathResolverDefinition->replaceArgument(1, $config['cache_prefix']); + + $pathResolverServiceId = 'liip_imagine.util.resolver.path'; + $container->setDefinition($pathResolverServiceId, $pathResolverDefinition); + + $resolverDefinition->replaceArgument(1, new Reference($pathResolverServiceId)); + + $resolverDefinition->addTag( + 'liip_imagine.cache.resolver', + [ + 'resolver' => $resolverName, + ] + ); + + $resolverId = 'liip_imagine.cache.resolver.'; + $container->setDefinition($resolverId.$resolverName, $resolverDefinition); + + return $resolverId; + } + + /** + * {@inheritdoc} + */ + public function addConfiguration(ArrayNodeDefinition $builder) + { + $builder + ->children() + ->scalarNode('web_root') + ->defaultValue(SymfonyFramework::getContainerResolvableRootWebPath()) + ->cannotBeEmpty() + ->end() + ->scalarNode('cache_prefix') + ->defaultValue('media/cache') + ->cannotBeEmpty() + ->end() + ->end(); + } +} diff --git a/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactory.php b/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactory.php new file mode 100644 index 000000000..7c24cd416 --- /dev/null +++ b/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactory.php @@ -0,0 +1,23 @@ +getChildResolverDefinition(); - $resolverDefinition->replaceArgument(2, $config['web_root']); - $resolverDefinition->replaceArgument(3, $config['cache_prefix']); - $resolverDefinition->addTag('liip_imagine.cache.resolver', [ - 'resolver' => $resolverName, - ]); - - $resolverId = 'liip_imagine.cache.resolver.'; - $container->setDefinition($resolverId.$resolverName, $resolverDefinition); - - return $resolverId; - } - /** * {@inheritdoc} */ @@ -42,22 +20,4 @@ public function getName() { return 'web_path'; } - - /** - * {@inheritdoc} - */ - public function addConfiguration(ArrayNodeDefinition $builder) - { - $builder - ->children() - ->scalarNode('web_root') - ->defaultValue(SymfonyFramework::getContainerResolvableRootWebPath()) - ->cannotBeEmpty() - ->end() - ->scalarNode('cache_prefix') - ->defaultValue('media/cache') - ->cannotBeEmpty() - ->end() - ->end(); - } } diff --git a/Imagine/Cache/Resolver/AbstractWebPathResolver.php b/Imagine/Cache/Resolver/AbstractWebPathResolver.php new file mode 100644 index 000000000..8da38efb7 --- /dev/null +++ b/Imagine/Cache/Resolver/AbstractWebPathResolver.php @@ -0,0 +1,100 @@ +filesystem = $filesystem; + $this->pathResolver = $pathResolver; + } + + /** + * Checks whether the given path is stored within this Resolver. + * + * @param string $path + * @param string $filter + * + * @return bool + */ + public function isStored($path, $filter) + { + return is_file($this->pathResolver->getFilePath($path, $filter)); + } + + /** + * {@inheritdoc} + */ + public function store(BinaryInterface $binary, $path, $filter) + { + $this->filesystem->dumpFile( + $this->pathResolver->getFilePath($path, $filter), + $binary->getContent() + ); + } + + /** + * {@inheritdoc} + */ + public function remove(array $paths, array $filters) + { + if (empty($paths) && empty($filters)) { + return; + } + + if (empty($paths)) { + $filtersCacheDir = []; + foreach ($filters as $filter) { + $filtersCacheDir[] = $this->pathResolver->getCacheRoot().'/'.$filter; + } + + $this->filesystem->remove($filtersCacheDir); + + return; + } + + foreach ($paths as $path) { + foreach ($filters as $filter) { + $this->filesystem->remove($this->pathResolver->getFilePath($path, $filter)); + } + } + } + + /** + * @return PathResolverInterface + */ + protected function getPathResolver() + { + return $this->pathResolver; + } +} diff --git a/Imagine/Cache/Resolver/RelativeWebPathResolver.php b/Imagine/Cache/Resolver/RelativeWebPathResolver.php new file mode 100644 index 000000000..7b61bcf85 --- /dev/null +++ b/Imagine/Cache/Resolver/RelativeWebPathResolver.php @@ -0,0 +1,23 @@ +getPathResolver()->getFileUrl($path, $filter)); + } +} diff --git a/Imagine/Cache/Resolver/WebPathResolver.php b/Imagine/Cache/Resolver/WebPathResolver.php index a39c54907..d6d7f359e 100644 --- a/Imagine/Cache/Resolver/WebPathResolver.php +++ b/Imagine/Cache/Resolver/WebPathResolver.php @@ -11,56 +11,26 @@ namespace Liip\ImagineBundle\Imagine\Cache\Resolver; -use Liip\ImagineBundle\Binary\BinaryInterface; -use Liip\ImagineBundle\Imagine\Cache\Helper\PathHelper; +use Liip\ImagineBundle\Utility\Path\PathResolverInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Routing\RequestContext; -class WebPathResolver implements ResolverInterface +class WebPathResolver extends AbstractWebPathResolver { - /** - * @var Filesystem - */ - protected $filesystem; - - /** - * @var RequestContext - */ - protected $requestContext; - - /** - * @var string - */ - protected $webRoot; + private $requestContext; /** - * @var string - */ - protected $cachePrefix; - - /** - * @var string - */ - protected $cacheRoot; - - /** - * @param Filesystem $filesystem - * @param RequestContext $requestContext - * @param string $webRootDir - * @param string $cachePrefix + * @param Filesystem $filesystem + * @param PathResolverInterface $pathResolver + * @param RequestContext $requestContext */ public function __construct( Filesystem $filesystem, - RequestContext $requestContext, - $webRootDir, - $cachePrefix = 'media/cache' + PathResolverInterface $pathResolver, + RequestContext $requestContext ) { - $this->filesystem = $filesystem; + parent::__construct($filesystem, $pathResolver); $this->requestContext = $requestContext; - - $this->webRoot = rtrim(str_replace('//', '/', $webRootDir), '/'); - $this->cachePrefix = ltrim(str_replace('//', '/', $cachePrefix), '/'); - $this->cacheRoot = $this->webRoot.'/'.$this->cachePrefix; } /** @@ -68,74 +38,13 @@ public function __construct( */ public function resolve($path, $filter) { - return sprintf('%s/%s', + return sprintf( + '%s/%s', rtrim($this->getBaseUrl(), '/'), - ltrim($this->getFileUrl($path, $filter), '/') + ltrim($this->getPathResolver()->getFileUrl($path, $filter), '/') ); } - /** - * {@inheritdoc} - */ - public function isStored($path, $filter) - { - return is_file($this->getFilePath($path, $filter)); - } - - /** - * {@inheritdoc} - */ - public function store(BinaryInterface $binary, $path, $filter) - { - $this->filesystem->dumpFile( - $this->getFilePath($path, $filter), - $binary->getContent() - ); - } - - /** - * {@inheritdoc} - */ - public function remove(array $paths, array $filters) - { - if (empty($paths) && empty($filters)) { - return; - } - - if (empty($paths)) { - $filtersCacheDir = []; - foreach ($filters as $filter) { - $filtersCacheDir[] = $this->cacheRoot.'/'.$filter; - } - - $this->filesystem->remove($filtersCacheDir); - - return; - } - - foreach ($paths as $path) { - foreach ($filters as $filter) { - $this->filesystem->remove($this->getFilePath($path, $filter)); - } - } - } - - /** - * {@inheritdoc} - */ - protected function getFilePath($path, $filter) - { - return $this->webRoot.'/'.$this->getFullPath($path, $filter); - } - - /** - * {@inheritdoc} - */ - protected function getFileUrl($path, $filter) - { - return PathHelper::filePathToUrlPath($this->getFullPath($path, $filter)); - } - /** * @return string */ @@ -156,19 +65,12 @@ protected function getBaseUrl() } $baseUrl = rtrim($baseUrl, '/\\'); - return sprintf('%s://%s%s%s', + return sprintf( + '%s://%s%s%s', $this->requestContext->getScheme(), $this->requestContext->getHost(), $port, $baseUrl ); } - - private function getFullPath($path, $filter) - { - // crude way of sanitizing URL scheme ("protocol") part - $path = str_replace('://', '---', $path); - - return $this->cachePrefix.'/'.$filter.'/'.ltrim($path, '/'); - } } diff --git a/LiipImagineBundle.php b/LiipImagineBundle.php index 44072bdb4..50a52a1b4 100644 --- a/LiipImagineBundle.php +++ b/LiipImagineBundle.php @@ -25,6 +25,7 @@ use Liip\ImagineBundle\DependencyInjection\Factory\Loader\StreamLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\AwsS3ResolverFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\FlysystemResolverFactory; +use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\RelativeWebPathResolverFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\WebPathResolverFactory; use Liip\ImagineBundle\DependencyInjection\LiipImagineExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -58,6 +59,7 @@ public function build(ContainerBuilder $container) $extension->addResolverFactory(new WebPathResolverFactory()); $extension->addResolverFactory(new AwsS3ResolverFactory()); $extension->addResolverFactory(new FlysystemResolverFactory()); + $extension->addResolverFactory(new RelativeWebPathResolverFactory()); $extension->addLoaderFactory(new StreamLoaderFactory()); $extension->addLoaderFactory(new FileSystemLoaderFactory()); diff --git a/Resources/config/imagine.xml b/Resources/config/imagine.xml index e7aa74a54..4e037764b 100644 --- a/Resources/config/imagine.xml +++ b/Resources/config/imagine.xml @@ -139,6 +139,11 @@ + + + + + @@ -300,14 +305,17 @@ - - + + + + + - - + + diff --git a/Resources/doc/cache-resolver/relative_web_path.rst b/Resources/doc/cache-resolver/relative_web_path.rst new file mode 100644 index 000000000..9b9708d3f --- /dev/null +++ b/Resources/doc/cache-resolver/relative_web_path.rst @@ -0,0 +1,67 @@ + +.. _cache-resolver-relative-web-path: + +Relative Web Path Resolver +================= + +This cache resolver (``RelativeWebPathResolver``), as well as +:ref:`web path cache resolver `, enables cache resolution for +local, web-path-based setups. This means images will be cached on your +local filesystem, within the web path of your Symfony application. +The only difference between them is that first one generates relative url paths + +Configuration +------------- +.. code-block:: yaml + + liip_imagine: + resolvers: + profile_photos: + relative_web_path: + # use %kernel.project_dir%/web for Symfony prior to 4.0.0 + web_root: "%kernel.project_dir%/public" + cache_prefix: "media/cache" + +There are several configuration options available: + +* ``web_root`` - must be the absolute path to you application's web root. This + is used to determine where to put generated image files, so that apache + will pick them up before handing the request to Symfony next time they + are requested. The default value ends with ``web`` for Symfony prior to + version ``4.0.0``. + Default value: ``%kernel.project_dir%/(public|web)`` +* ``cache_prefix`` - this is also used in the path for image generation, so + as to not clutter your web root with cached images. For example by default, + the images would be written to the ``web/media/cache/`` directory. + Default value: ``/media/cache`` + +Usage +----- +After configuring ``RelativeWebPathResolver``, you can set it as the default cache resolver +for ``LiipImagineBundle`` using the following configuration. + +.. code-block:: yaml + + # app/config/config.yml + + liip_imagine: + cache: profile_photos + + +Usage on a Specific Filter +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Alternatively, you can set ``RelativeWebPathResolver`` as the cache resolver for a specific +filter set using the following configuration. + +.. code-block:: yaml + + # app/config/config.yml + + liip_imagine: + filter_sets: + cache: ~ + my_thumb: + cache: profile_photos + filters: + # the filter list diff --git a/Tests/DependencyInjection/Factory/Resolver/AbstractWebPathResolverTest.php b/Tests/DependencyInjection/Factory/Resolver/AbstractWebPathResolverTest.php new file mode 100644 index 000000000..ed6c21db2 --- /dev/null +++ b/Tests/DependencyInjection/Factory/Resolver/AbstractWebPathResolverTest.php @@ -0,0 +1,158 @@ +getClassName()); + + $this->assertTrue($rc->implementsInterface(ResolverFactoryInterface::class)); + } + + public function testCouldBeConstructedWithoutAnyArguments(): void + { + $loader = $this->createResolver(); + + $this->assertInstanceOf($this->getClassName(), $loader); + } + + public function testAbstractWebPathResolverFactoryImplementation(): void + { + $this->assertTrue(is_a($this->getClassName(), AbstractWebPathResolverFactory::class, true)); + } + + public function testCreateResolverDefinitionOnCreate(): void + { + $container = new ContainerBuilder(); + + $resolver = $this->createResolver(); + $resolver->create( + $container, + 'the_resolver_name', + [ + 'web_root' => 'theWebRoot', + 'cache_prefix' => 'theCachePrefix', + ] + ); + + $this->assertTrue($container->hasDefinition('liip_imagine.cache.resolver.the_resolver_name')); + + $resolverDefinition = $container->getDefinition('liip_imagine.cache.resolver.the_resolver_name'); + $this->assertInstanceOf(ChildDefinition::class, $resolverDefinition); + $this->assertSame( + sprintf('liip_imagine.cache.resolver.prototype.%s', $resolver->getName()), + $resolverDefinition->getParent() + ); + + $utilPathResolverReference = $resolverDefinition->getArgument(1); + $this->assertInstanceOf(Reference::class, $utilPathResolverReference); + + $utilPathResolverServiceId = (string) $utilPathResolverReference; + $this->assertSame('liip_imagine.util.resolver.path', $utilPathResolverServiceId); + $this->assertTrue($container->hasDefinition($utilPathResolverServiceId)); + + $utilPathResolverDefinition = $container->getDefinition($utilPathResolverServiceId); + $this->assertInstanceOf(ChildDefinition::class, $utilPathResolverDefinition); + $this->assertSame('liip_imagine.util.resolver.prototype.path', $utilPathResolverDefinition->getParent()); + + $this->assertSame('theWebRoot', $utilPathResolverDefinition->getArgument(0)); + $this->assertSame('theCachePrefix', $utilPathResolverDefinition->getArgument(1)); + } + + public function testProcessCorrectlyOptionsOnAddConfiguration(): void + { + $expectedWebPath = 'theWebPath'; + $expectedCachePrefix = 'theCachePrefix'; + + $treeBuilder = new TreeBuilder('test_resolver_name'); + $rootNode = method_exists(TreeBuilder::class, 'getRootNode') + ? $treeBuilder->getRootNode() + : $treeBuilder->root('test_resolver_name'); + + $resolver = $this->createResolver(); + $resolver->addConfiguration($rootNode); + + $config = $this->processConfigTree( + $treeBuilder, + [ + $resolver->getName() => [ + 'web_root' => $expectedWebPath, + 'cache_prefix' => $expectedCachePrefix, + ], + ] + ); + + $this->assertArrayHasKey('web_root', $config); + $this->assertSame($expectedWebPath, $config['web_root']); + + $this->assertArrayHasKey('cache_prefix', $config); + $this->assertSame($expectedCachePrefix, $config['cache_prefix']); + } + + public function testAddDefaultOptionsIfNotSetOnAddConfiguration(): void + { + $treeBuilder = new TreeBuilder('test_resolver_name'); + $rootNode = method_exists(TreeBuilder::class, 'getRootNode') + ? $treeBuilder->getRootNode() + : $treeBuilder->root('test_resolver_name'); + + $resolver = $this->createResolver(); + $resolver->addConfiguration($rootNode); + + $config = $this->processConfigTree( + $treeBuilder, + [ + $resolver->getName() => [], + ] + ); + + $this->assertArrayHasKey('web_root', $config); + $this->assertSame(SymfonyFramework::getContainerResolvableRootWebPath(), $config['web_root']); + + $this->assertArrayHasKey('cache_prefix', $config); + $this->assertSame('media/cache', $config['cache_prefix']); + } + + protected function processConfigTree(TreeBuilder $treeBuilder, array $configs): array + { + $processor = new Processor(); + + return $processor->process($treeBuilder->buildTree(), $configs); + } + + /** + * @return string|ResolverFactoryInterface + */ + abstract protected function getClassName(); + + private function createResolver() + { + $className = $this->getClassName(); + + return new $className(); + } +} diff --git a/Tests/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactoryTest.php b/Tests/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactoryTest.php new file mode 100644 index 000000000..89da238a9 --- /dev/null +++ b/Tests/DependencyInjection/Factory/Resolver/RelativeWebPathResolverFactoryTest.php @@ -0,0 +1,36 @@ +assertSame('relative_web_path', $resolver->getName()); + } + + /** + * @return string|ResolverFactoryInterface + */ + protected function getClassName() + { + return RelativeWebPathResolverFactory::class; + } +} diff --git a/Tests/DependencyInjection/Factory/Resolver/WebPathResolverFactoryTest.php b/Tests/DependencyInjection/Factory/Resolver/WebPathResolverFactoryTest.php index ce966277d..829a86393 100644 --- a/Tests/DependencyInjection/Factory/Resolver/WebPathResolverFactoryTest.php +++ b/Tests/DependencyInjection/Factory/Resolver/WebPathResolverFactoryTest.php @@ -13,118 +13,24 @@ use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\ResolverFactoryInterface; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\WebPathResolverFactory; -use Liip\ImagineBundle\Utility\Framework\SymfonyFramework; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; -use Symfony\Component\Config\Definition\Processor; -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @covers \Liip\ImagineBundle\DependencyInjection\Factory\Resolver\WebPathResolverFactory */ -class WebPathResolverFactoryTest extends TestCase +class WebPathResolverFactoryTest extends AbstractWebPathResolverTest { - public function testImplementsResolverFactoryInterface() - { - $rc = new \ReflectionClass(WebPathResolverFactory::class); - - $this->assertTrue($rc->implementsInterface(ResolverFactoryInterface::class)); - } - - public function testCouldBeConstructedWithoutAnyArguments() - { - $loader = new WebPathResolverFactory(); - - $this->assertInstanceOf(WebPathResolverFactory::class, $loader); - } - - public function testReturnExpectedName() + public function testReturnExpectedName(): void { $resolver = new WebPathResolverFactory(); $this->assertSame('web_path', $resolver->getName()); } - public function testCreateResolverDefinitionOnCreate() - { - $container = new ContainerBuilder(); - - $resolver = new WebPathResolverFactory(); - - $resolver->create($container, 'the_resolver_name', [ - 'web_root' => 'theWebRoot', - 'cache_prefix' => 'theCachePrefix', - ]); - - $this->assertTrue($container->hasDefinition('liip_imagine.cache.resolver.the_resolver_name')); - - $resolverDefinition = $container->getDefinition('liip_imagine.cache.resolver.the_resolver_name'); - $this->assertInstanceOf(ChildDefinition::class, $resolverDefinition); - $this->assertSame('liip_imagine.cache.resolver.prototype.web_path', $resolverDefinition->getParent()); - - $this->assertSame('theWebRoot', $resolverDefinition->getArgument(2)); - $this->assertSame('theCachePrefix', $resolverDefinition->getArgument(3)); - } - - public function testProcessCorrectlyOptionsOnAddConfiguration() - { - $expectedWebPath = 'theWebPath'; - $expectedCachePrefix = 'theCachePrefix'; - - $treeBuilder = new TreeBuilder('web_path'); - $rootNode = method_exists(TreeBuilder::class, 'getRootNode') - ? $treeBuilder->getRootNode() - : $treeBuilder->root('web_path'); - - $resolver = new WebPathResolverFactory(); - $resolver->addConfiguration($rootNode); - - $config = $this->processConfigTree($treeBuilder, [ - 'web_path' => [ - 'web_root' => $expectedWebPath, - 'cache_prefix' => $expectedCachePrefix, - ], - ]); - - $this->assertArrayHasKey('web_root', $config); - $this->assertSame($expectedWebPath, $config['web_root']); - - $this->assertArrayHasKey('cache_prefix', $config); - $this->assertSame($expectedCachePrefix, $config['cache_prefix']); - } - - public function testAddDefaultOptionsIfNotSetOnAddConfiguration() - { - $treeBuilder = new TreeBuilder('web_path'); - $rootNode = method_exists(TreeBuilder::class, 'getRootNode') - ? $treeBuilder->getRootNode() - : $treeBuilder->root('web_path'); - - $resolver = new WebPathResolverFactory(); - $resolver->addConfiguration($rootNode); - - $config = $this->processConfigTree($treeBuilder, [ - 'web_path' => [], - ]); - - $this->assertArrayHasKey('web_root', $config); - $this->assertSame(SymfonyFramework::getContainerResolvableRootWebPath(), $config['web_root']); - - $this->assertArrayHasKey('cache_prefix', $config); - $this->assertSame('media/cache', $config['cache_prefix']); - } - /** - * @param TreeBuilder $treeBuilder - * @param array $configs - * - * @return array + * @return string|ResolverFactoryInterface */ - protected function processConfigTree(TreeBuilder $treeBuilder, array $configs) + protected function getClassName() { - $processor = new Processor(); - - return $processor->process($treeBuilder->buildTree(), $configs); + return WebPathResolverFactory::class; } } diff --git a/Tests/Imagine/Cache/Resolver/RelativeWebPathResolverTest.php b/Tests/Imagine/Cache/Resolver/RelativeWebPathResolverTest.php new file mode 100644 index 000000000..0ca0ed762 --- /dev/null +++ b/Tests/Imagine/Cache/Resolver/RelativeWebPathResolverTest.php @@ -0,0 +1,243 @@ +filesystem = $this->getMockBuilder(Filesystem::class)->getMock(); + $this->pathResolverUtil = $this->getMockBuilder(PathResolverInterface::class)->getMock(); + $this->relativeWebPathResolver = new RelativeWebPathResolver($this->filesystem, $this->pathResolverUtil); + + $this->basePath = sys_get_temp_dir().'/aWebRoot'; + } + + public function testImplementsResolverInterface(): void + { + $this->assertInstanceOf(ResolverInterface::class, $this->relativeWebPathResolver); + } + + public function testResolve(): void + { + $path = 'aPath'; + $filter = 'aFilter'; + $fileUrl = 'cacheDir/aFilter/aPath'; + + $this->pathResolverUtil + ->expects($this->once()) + ->method('getFileUrl') + ->with($this->equalTo($path), $this->equalTo($filter)) + ->willReturn($fileUrl); + + $actualFileUrl = $this->relativeWebPathResolver->resolve($path, $filter); + + $this->assertSame(sprintf('/%s', $fileUrl), $actualFileUrl); + } + + public function testOnSameConstructorArguments(): void + { + $this->assertAttributeSame($this->filesystem, 'filesystem', $this->relativeWebPathResolver); + $this->assertAttributeSame($this->pathResolverUtil, 'pathResolver', $this->relativeWebPathResolver); + } + + public function testFileIsStored(): void + { + $existingFile = $this->basePath.'/aCachePrefix/aFilter/existingPath'; + $filesystem = new Filesystem(); + $filesystem->mkdir(dirname($existingFile)); + $filesystem->touch($existingFile); + + $pathResolver = new PathResolver($this->basePath, 'aCachePrefix'); + $resolver = new RelativeWebPathResolver( + $this->filesystem, + $pathResolver + ); + + $this->assertTrue($resolver->isStored('existingPath', 'aFilter')); + $filesystem->remove($this->basePath); + } + + public function testFileIsNotStored(): void + { + $existingFile = $this->basePath.'/aCachePrefix/aFilter/existingPath'; + $filesystem = new Filesystem(); + $filesystem->mkdir(dirname($existingFile)); + $filesystem->touch($existingFile); + + $pathResolver = new PathResolver($this->basePath, 'aCachePrefix'); + $resolver = new RelativeWebPathResolver( + $this->filesystem, + $pathResolver + ); + + $this->assertFalse($resolver->isStored('notExisting file', 'aFilter')); + $filesystem->remove($this->basePath); + } + + public function testStore(): void + { + $path = 'aPath'; + $filter = 'aFilter'; + $filePath = '/rootDir/cacheDir/file'; + $fileContent = 'theFileContent'; + + $binary = new Binary($fileContent, 'applivation/customFile', 'custom'); + + $this->pathResolverUtil + ->expects($this->once()) + ->method('getFilePath') + ->with($this->equalTo($path), $this->equalTo($filter)) + ->willReturn($filePath); + + $this->filesystem + ->expects($this->once()) + ->method('dumpFile') + ->with($this->equalTo($filePath), $this->equalTo($fileContent)); + + $this->relativeWebPathResolver->store($binary, $path, $filter); + } + + public function testRemoveWithEmptyInputArrays(): void + { + $this->filesystem + ->expects($this->exactly(0)) + ->method('remove'); + + $this->relativeWebPathResolver->remove([], []); + } + + public function testRemoveWithEmptyPathsArrayAndSingleFilter(): void + { + $filter = 'aFilter'; + $cacheRoot = '/root/cacheFolder'; + + $this->pathResolverUtil + ->expects($this->once()) + ->method('getCacheRoot') + ->willReturn($cacheRoot); + + $this->filesystem + ->expects($this->once()) + ->method('remove') + ->with( + $this->equalTo( + [ + sprintf('%s/%s', $cacheRoot, $filter), + ] + ) + ); + + $this->relativeWebPathResolver->remove([], [$filter]); + } + + public function testRemoveWithEmptyPathsArrayAndMultipleFilters(): void + { + $filterOne = 'aFilterOne'; + $filterTwo = 'aFilterTwo'; + $cacheRoot = '/root/cacheFolder'; + + $this->pathResolverUtil + ->expects($this->exactly(2)) + ->method('getCacheRoot') + ->willReturn($cacheRoot); + + $this->filesystem + ->expects($this->once()) + ->method('remove') + ->with( + $this->equalTo( + [ + sprintf('%s/%s', $cacheRoot, $filterOne), + sprintf('%s/%s', $cacheRoot, $filterTwo), + ] + ) + ); + + $this->relativeWebPathResolver->remove([], [$filterOne, $filterTwo]); + } + + public function testRemoveWithMultiplePathaAndFilters(): void + { + $filterOne = 'aFilterOne'; + $filterTwo = 'aFilterTwo'; + $pathOne = 'aPathOne'; + $pathTwo = 'aPathTwo'; + $cacheRoot = '/root/cacheFolder'; + + $this->pathResolverUtil + ->expects($this->exactly(0)) + ->method('getCacheRoot'); + + $this->pathResolverUtil + ->method('getFilePath') + ->willReturnMap( + [ + [$pathOne, $filterOne, sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathOne)], + [$pathOne, $filterTwo, sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathOne)], + [$pathTwo, $filterOne, sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathTwo)], + [$pathTwo, $filterTwo, sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathTwo)], + ] + ); + + $this->filesystem + ->expects($this->at(0)) + ->method('remove') + ->with(sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathOne)); + $this->filesystem + ->expects($this->at(1)) + ->method('remove') + ->with(sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathOne)); + $this->filesystem + ->expects($this->at(2)) + ->method('remove') + ->with(sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathTwo)); + $this->filesystem + ->expects($this->at(3)) + ->method('remove') + ->with(sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathTwo)); + + $this->relativeWebPathResolver->remove( + [$pathOne, $pathTwo], + [$filterOne, $filterTwo] + ); + } +} diff --git a/Tests/Imagine/Cache/Resolver/WebPathResolverTest.php b/Tests/Imagine/Cache/Resolver/WebPathResolverTest.php index 0eb27b54a..28deee5fc 100644 --- a/Tests/Imagine/Cache/Resolver/WebPathResolverTest.php +++ b/Tests/Imagine/Cache/Resolver/WebPathResolverTest.php @@ -14,6 +14,8 @@ use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface; use Liip\ImagineBundle\Imagine\Cache\Resolver\WebPathResolver; use Liip\ImagineBundle\Model\Binary; +use Liip\ImagineBundle\Utility\Path\PathResolver; +use Liip\ImagineBundle\Utility\Path\PathResolverInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Routing\RequestContext; @@ -62,83 +64,23 @@ public function testImplementsResolverInterface() public function testCouldBeConstructedWithRequiredArguments() { $filesystemMock = $this->createFilesystemMock(); + $pathResolver = $this->createPathResolverMock(); $requestContext = new RequestContext(); - $webRoot = 'theWebRoot'; - $resolver = new WebPathResolver($filesystemMock, $requestContext, $webRoot); + $resolver = new WebPathResolver($filesystemMock, $pathResolver, $requestContext); $this->assertAttributeSame($filesystemMock, 'filesystem', $resolver); + $this->assertAttributeSame($pathResolver, 'pathResolver', $resolver); $this->assertAttributeSame($requestContext, 'requestContext', $resolver); - $this->assertAttributeSame($webRoot, 'webRoot', $resolver); - } - - public function testCouldBeConstructedWithOptionalArguments() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - 'aWebRoot', - 'theCachePrefix' - ); - - $this->assertAttributeSame('theCachePrefix', 'cachePrefix', $resolver); - } - - public function testTrimRightSlashFromWebPathOnConstruct() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - 'aWebRoot/', - 'theCachePrefix' - ); - - $this->assertAttributeSame('aWebRoot', 'webRoot', $resolver); - } - - public function testRemoveDoubleSlashFromWebRootOnConstruct() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - 'aWeb//Root', - '/aCachePrefix' - ); - - $this->assertAttributeSame('aWeb/Root', 'webRoot', $resolver); - } - - public function testTrimRightSlashFromCachePrefixOnConstruct() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - 'aWebRoot', - '/aCachePrefix' - ); - - $this->assertAttributeSame('aCachePrefix', 'cachePrefix', $resolver); - } - - public function testRemoveDoubleSlashFromCachePrefixOnConstruct() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - 'aWebRoot', - 'aCache//Prefix' - ); - - $this->assertAttributeSame('aCache/Prefix', 'cachePrefix', $resolver); } public function testReturnTrueIfFileExistsOnIsStored() { + $pathResolver = new PathResolver($this->basePath, 'aCachePrefix'); $resolver = new WebPathResolver( $this->createFilesystemMock(), - new RequestContext(), - $this->basePath, - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $this->assertTrue($resolver->isStored('existingPath', 'aFilter')); @@ -146,11 +88,11 @@ public function testReturnTrueIfFileExistsOnIsStored() public function testReturnFalseIfFileNotExistsOnIsStored() { + $pathResolver = new PathResolver($this->basePath, 'aCachePrefix'); $resolver = new WebPathResolver( $this->createFilesystemMock(), - new RequestContext(), - $this->basePath, - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $this->assertFalse($resolver->isStored('nonExistingPath', 'aFilter')); @@ -158,11 +100,11 @@ public function testReturnFalseIfFileNotExistsOnIsStored() public function testReturnFalseIfIsNotFile() { + $pathResolver = new PathResolver($this->basePath, 'aCachePrefix'); $resolver = new WebPathResolver( $this->createFilesystemMock(), - new RequestContext(), - $this->basePath, - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $this->assertFalse($resolver->isStored('', 'aFilter')); @@ -170,40 +112,61 @@ public function testReturnFalseIfIsNotFile() public function testComposeSchemaHostAndFileUrlOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $requestContext = new RequestContext(); $requestContext->setScheme('theSchema'); $requestContext->setHost('thehost'); + $pathResolver = $this->createPathResolverMock(); + $pathResolver + ->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn( + 'aCachePrefix/aFilter/aPath' + ); + $resolver = new WebPathResolver( - $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $this->createFilesystemMock(), $pathResolver, $requestContext ); $this->assertSame( 'theschema://thehost/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + $resolver->resolve($path, $filter) ); } public function testComposeSchemaHostAndBasePathWithPhpFileAndFileUrlOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $requestContext = new RequestContext(); $requestContext->setScheme('theSchema'); $requestContext->setHost('thehost'); $requestContext->setBaseUrl('/theBasePath/app.php'); + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn( + 'aCachePrefix/aFilter/aPath' + ); + $resolver = new WebPathResolver( - $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $this->createFilesystemMock(), $pathResolver, $requestContext ); $this->assertSame( 'theschema://thehost/theBasePath/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + $resolver->resolve($path, $filter) ); } @@ -214,11 +177,12 @@ public function testResolveWithPrefixCacheEmpty() $requestContext->setHost('thehost'); $requestContext->setBaseUrl('/theBasePath/app.php'); + $pathResolver = new PathResolver('/aWebRoot', ''); + $resolver = new WebPathResolver( $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - '' + $pathResolver, + $requestContext ); $this->assertSame( @@ -229,116 +193,173 @@ public function testResolveWithPrefixCacheEmpty() public function testComposeSchemaHostAndBasePathWithDirsOnlyAndFileUrlOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $requestContext = new RequestContext(); $requestContext->setScheme('theSchema'); $requestContext->setHost('thehost'); $requestContext->setBaseUrl('/theBasePath/theSubBasePath'); + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn( + 'aCachePrefix/aFilter/aPath' + ); + $resolver = new WebPathResolver( - $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $this->createFilesystemMock(), $pathResolver, $requestContext ); $this->assertSame( 'theschema://thehost/theBasePath/theSubBasePath/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + $resolver->resolve($path, $filter) ); } public function testComposeSchemaHostAndBasePathWithBackSplashOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $fileUrl = 'aCachePrefix/aFilter/aPath'; + $requestContext = new RequestContext(); $requestContext->setScheme('theSchema'); $requestContext->setHost('thehost'); $requestContext->setBaseUrl('\\'); + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn($fileUrl); + $resolver = new WebPathResolver( $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + $requestContext ); $this->assertSame( - 'theschema://thehost/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + sprintf('theschema://thehost/%s', $fileUrl), + $resolver->resolve($path, $filter) ); } public function testComposeSchemaHttpAndCustomPortAndFileUrlOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $fileUrl = 'aCachePrefix/aFilter/aPath'; + $requestContext = new RequestContext(); $requestContext->setScheme('http'); $requestContext->setHost('thehost'); $requestContext->setHttpPort(88); + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn($fileUrl); + $resolver = new WebPathResolver( $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + $requestContext ); $this->assertSame( - 'http://thehost:88/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + sprintf('http://thehost:88/%s', $fileUrl), + $resolver->resolve($path, $filter) ); } public function testComposeSchemaHttpsAndCustomPortAndFileUrlOnResolve() { + $path = 'aPath'; + $filter = 'aFilter'; + $fileUrl = 'aCachePrefix/aFilter/aPath'; + $requestContext = new RequestContext(); $requestContext->setScheme('https'); $requestContext->setHost('thehost'); $requestContext->setHttpsPort(444); + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFileUrl') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn($fileUrl); + $resolver = new WebPathResolver( $this->createFilesystemMock(), - $requestContext, - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + $requestContext ); $this->assertSame( - 'https://thehost:444/aCachePrefix/aFilter/aPath', - $resolver->resolve('aPath', 'aFilter') + sprintf('https://thehost:444/%s', $fileUrl), + $resolver->resolve($path, $filter) ); } public function testDumpBinaryContentOnStore() { - $binary = new Binary('theContent', 'aMimeType', 'aFormat'); + $path = 'aPath'; + $filter = 'aFilter'; + $fileUrl = '/aWebRoot/aCachePrefix/aFilter/aPath'; + $fileContent = 'theContent'; + + $binary = new Binary($fileContent, 'aMimeType', 'aFormat'); $filesystemMock = $this->createFilesystemMock(); $filesystemMock ->expects($this->once()) ->method('dumpFile') - ->with('/aWebRoot/aCachePrefix/aFilter/aPath', 'theContent'); + ->with( + $this->equalTo($fileUrl), + $this->equalTo($fileContent) + ); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFilePath') + ->with( + $this->equalTo($path), + $this->equalTo($filter) + ) + ->willReturn( + $fileUrl + ); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); - $this->assertNull($resolver->store($binary, 'aPath', 'aFilter')); + $this->assertNull($resolver->store($binary, $path, $filter)); } public function testDoNothingIfFiltersAndPathsEmptyOnRemove() { $filesystemMock = $this->createFilesystemMock(); - $filesystemMock - ->expects($this->never()) - ->method('remove'); + $filesystemMock->expects($this->never())->method('remove'); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $this->createPathResolverMock(), + new RequestContext() ); $resolver->remove([], []); @@ -346,17 +367,17 @@ public function testDoNothingIfFiltersAndPathsEmptyOnRemove() public function testRemoveCacheForPathAndFilterOnRemove() { + $filePath = '/aWebRoot/aCachePrefix/aFilter/aPath'; $filesystemMock = $this->createFilesystemMock(); - $filesystemMock - ->expects($this->once()) - ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilter/aPath'); + $filesystemMock->expects($this->once())->method('remove')->with($filePath); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFilePath')->willReturn($filePath); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $resolver->remove(['aPath'], ['aFilter']); @@ -364,74 +385,124 @@ public function testRemoveCacheForPathAndFilterOnRemove() public function testRemoveCacheForSomePathsAndFilterOnRemove() { + $cacheRoot = '/aWebRoot/aCachePrefix'; + $pathOne = 'aPathOne'; + $pathTwo = 'aPathTwo'; + $filter = 'aFilter'; + $filesystemMock = $this->createFilesystemMock(); $filesystemMock ->expects($this->at(0)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilter/aPathOne'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filter, $pathOne) + ); $filesystemMock ->expects($this->at(1)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilter/aPathTwo'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filter, $pathTwo) + ); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver->method('getFilePath') + ->willReturnMap( + [ + [$pathOne, $filter, sprintf('%s/%s/%s', $cacheRoot, $filter, $pathOne)], + [$pathTwo, $filter, sprintf('%s/%s/%s', $cacheRoot, $filter, $pathTwo)], + ] + ); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); - $resolver->remove(['aPathOne', 'aPathTwo'], ['aFilter']); + $resolver->remove([$pathOne, $pathTwo], [$filter]); } public function testRemoveCacheForSomePathsAndSomeFiltersOnRemove() { + $cacheRoot = '/aWebRoot/aCachePrefix'; + $pathOne = 'aPathOne'; + $pathTwo = 'aPathTwo'; + $filterOne = 'aFilterOne'; + $filterTwo = 'aFilterTwo'; + $filesystemMock = $this->createFilesystemMock(); $filesystemMock ->expects($this->at(0)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilterOne/aPathOne'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathOne) + ); $filesystemMock ->expects($this->at(1)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilterTwo/aPathOne'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathOne) + ); $filesystemMock ->expects($this->at(2)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilterOne/aPathTwo'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathTwo) + ); $filesystemMock ->expects($this->at(3)) ->method('remove') - ->with('/aWebRoot/aCachePrefix/aFilterTwo/aPathTwo'); + ->with( + sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathTwo) + ); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver + ->method('getFilePath') + ->willReturnMap( + [ + [$pathOne, $filterOne, sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathOne)], + [$pathOne, $filterTwo, sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathOne)], + [$pathTwo, $filterOne, sprintf('%s/%s/%s', $cacheRoot, $filterOne, $pathTwo)], + [$pathTwo, $filterTwo, sprintf('%s/%s/%s', $cacheRoot, $filterTwo, $pathTwo)], + ] + ); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $resolver->remove( - ['aPathOne', 'aPathTwo'], - ['aFilterOne', 'aFilterTwo'] + [$pathOne, $pathTwo], + [$filterOne, $filterTwo] ); } public function testRemoveCacheForFilterOnRemove() { + $cacheRoot = '/aWebRoot/aCachePrefix'; + $filesystemMock = $this->createFilesystemMock(); $filesystemMock ->expects($this->once()) ->method('remove') - ->with([ - '/aWebRoot/aCachePrefix/aFilter', - ]); + ->with( + [ + sprintf('%s/aFilter', $cacheRoot), + ] + ); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver + ->method('getCacheRoot') + ->willReturn($cacheRoot); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $resolver->remove([], ['aFilter']); @@ -439,59 +510,39 @@ public function testRemoveCacheForFilterOnRemove() public function testRemoveCacheForSomeFiltersOnRemove() { + $cacheRoot = '/aWebRoot/aCachePrefix'; + $filesystemMock = $this->createFilesystemMock(); $filesystemMock ->expects($this->once()) ->method('remove') - ->with([ - '/aWebRoot/aCachePrefix/aFilterOne', - '/aWebRoot/aCachePrefix/aFilterTwo', - ]); + ->with( + [ + sprintf('%s/aFilterOne', $cacheRoot), + sprintf('%s/aFilterTwo', $cacheRoot), + ] + ); + + $pathResolver = $this->createPathResolverMock(); + $pathResolver + ->method('getCacheRoot') + ->willReturn($cacheRoot); $resolver = new WebPathResolver( $filesystemMock, - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' + $pathResolver, + new RequestContext() ); $resolver->remove([], ['aFilterOne', 'aFilterTwo']); } - public function testShouldRemoveDoubleSlashInUrl() - { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' - ); - - $rc = new \ReflectionClass($resolver); - $method = $rc->getMethod('getFileUrl'); - $method->setAccessible(true); - - $result = $method->invokeArgs($resolver, ['/cats.jpg', 'some_filter']); - - $this->assertSame('aCachePrefix/some_filter/cats.jpg', $result); - } - - public function testShouldSanitizeSeparatorBetweenSchemeAndAuthorityInUrl() + /** + * @return \PHPUnit\Framework\MockObject\MockObject|PathResolverInterface + */ + public function createPathResolverMock() { - $resolver = new WebPathResolver( - $this->createFilesystemMock(), - new RequestContext(), - '/aWebRoot', - 'aCachePrefix' - ); - - $rc = new \ReflectionClass($resolver); - $method = $rc->getMethod('getFileUrl'); - $method->setAccessible(true); - - $result = $method->invokeArgs($resolver, ['https://some.meme.com/cute/cats.jpg', 'some_filter']); - - $this->assertSame('aCachePrefix/some_filter/https---some.meme.com/cute/cats.jpg', $result); + return $this->getMockBuilder(PathResolverInterface::class)->getMock(); } /** diff --git a/Tests/LiipImagineBundleTest.php b/Tests/LiipImagineBundleTest.php index 49cabb3e5..56a0ae930 100644 --- a/Tests/LiipImagineBundleTest.php +++ b/Tests/LiipImagineBundleTest.php @@ -21,6 +21,7 @@ use Liip\ImagineBundle\DependencyInjection\Factory\Loader\StreamLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\AwsS3ResolverFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\FlysystemResolverFactory; +use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\RelativeWebPathResolverFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Resolver\WebPathResolverFactory; use Liip\ImagineBundle\DependencyInjection\LiipImagineExtension; use Liip\ImagineBundle\LiipImagineBundle; @@ -146,8 +147,7 @@ public function testAddAwsS3ResolverFactoryOnBuild() public function testAddFlysystemResolverFactoryOnBuild() { $extensionMock = $this->createLiipImagineExtensionMock(); - $extensionMock - ->expects($this->at(2)) + $extensionMock->expects($this->at(2)) ->method('addResolverFactory') ->with($this->isInstanceOf(FlysystemResolverFactory::class)); @@ -156,25 +156,29 @@ public function testAddFlysystemResolverFactoryOnBuild() ->expects($this->atLeastOnce()) ->method('getExtension') ->with('liip_imagine') - ->willReturn($extensionMock); + ->willReturn( + $extensionMock + ); $bundle = new LiipImagineBundle(); $bundle->build($containerMock); } - public function testAddChainLoaderFactoryOnBuild() + public function testAddRelativeWebPAthResolverFactoryOnBuild() { $extensionMock = $this->createLiipImagineExtensionMock(); $extensionMock - ->expects($this->at(6)) - ->method('addLoaderFactory') - ->with($this->isInstanceOf(ChainLoaderFactory::class)); + ->expects($this->at(3)) + ->method('addResolverFactory') + ->with($this->isInstanceOf(RelativeWebPathResolverFactory::class)); + $containerMock = $this->createContainerBuilderMock(); $containerMock ->expects($this->atLeastOnce()) ->method('getExtension') ->with('liip_imagine') ->willReturn($extensionMock); + $bundle = new LiipImagineBundle(); $bundle->build($containerMock); } @@ -183,7 +187,7 @@ public function testAddStreamLoaderFactoryOnBuild() { $extensionMock = $this->createLiipImagineExtensionMock(); $extensionMock - ->expects($this->at(3)) + ->expects($this->at(4)) ->method('addLoaderFactory') ->with($this->isInstanceOf(StreamLoaderFactory::class)); @@ -202,7 +206,7 @@ public function testAddFilesystemLoaderFactoryOnBuild() { $extensionMock = $this->createLiipImagineExtensionMock(); $extensionMock - ->expects($this->at(4)) + ->expects($this->at(5)) ->method('addLoaderFactory') ->with($this->isInstanceOf(FileSystemLoaderFactory::class)); @@ -221,7 +225,7 @@ public function testAddFlysystemLoaderFactoryOnBuild() { $extensionMock = $this->createLiipImagineExtensionMock(); $extensionMock - ->expects($this->at(5)) + ->expects($this->at(6)) ->method('addLoaderFactory') ->with($this->isInstanceOf(FlysystemLoaderFactory::class)); @@ -236,6 +240,23 @@ public function testAddFlysystemLoaderFactoryOnBuild() $bundle->build($containerMock); } + public function testAddChainLoaderFactoryOnBuild() + { + $extensionMock = $this->createLiipImagineExtensionMock(); + $extensionMock + ->expects($this->at(7)) + ->method('addLoaderFactory') + ->with($this->isInstanceOf(ChainLoaderFactory::class)); + $containerMock = $this->createContainerBuilderMock(); + $containerMock + ->expects($this->atLeastOnce()) + ->method('getExtension') + ->with('liip_imagine') + ->willReturn($extensionMock); + $bundle = new LiipImagineBundle(); + $bundle->build($containerMock); + } + /** * @return \PHPUnit_Framework_MockObject_MockObject|ContainerBuilder */ @@ -249,10 +270,14 @@ protected function createContainerBuilderMock() */ protected function createLiipImagineExtensionMock() { - return $this->createObjectMock(LiipImagineExtension::class, [ - 'getNamespace', - 'addResolverFactory', - 'addLoaderFactory', - ], false); + return $this->createObjectMock( + LiipImagineExtension::class, + [ + 'getNamespace', + 'addResolverFactory', + 'addLoaderFactory', + ], + false + ); } } diff --git a/Tests/Utility/Path/PathResolverTest.php b/Tests/Utility/Path/PathResolverTest.php new file mode 100644 index 000000000..74f7ea645 --- /dev/null +++ b/Tests/Utility/Path/PathResolverTest.php @@ -0,0 +1,221 @@ +assertTrue(is_a(PathResolver::class, PathResolverInterface::class, true)); + } + + public function testPropertiesForSameConstructorArguments(): void + { + $webRootDir = 'aWebRootDir'; + $cachePrefix = 'aCachePrefix'; + $pathResolver = new PathResolver($webRootDir, $cachePrefix); + $this->assertAttributeSame($webRootDir, 'webRoot', $pathResolver); + $this->assertAttributeSame($cachePrefix, 'cachePrefix', $pathResolver); + } + + public function testWebRootPathNormalizer(): void + { + $pathResolver = new PathResolver('aWebRootDir/'); + $this->assertAttributeSame('aWebRootDir', 'webRoot', $pathResolver); + } + + public function testCachePrefixNormalizer(): void + { + $pathResolver = new PathResolver('aWebRootDir', '/cachePrefix'); + $this->assertAttributeSame('cachePrefix', 'cachePrefix', $pathResolver); + } + + public function testForDoubleSlashReplacing(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir//subRootDir', + 'cachePrefix//subCacheDir' + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir', + 'webRoot', + $pathResolver + ); + $this->assertAttributeSame( + 'cachePrefix/subCacheDir', + 'cachePrefix', + $pathResolver + ); + } + + public function testPathsNormalizerWithSubfolders(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir/subRootDir', + 'cachePrefix/subCacheDir/anotherSubCacheDir' + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir', + 'webRoot', + $pathResolver + ); + $this->assertAttributeSame( + 'cachePrefix/subCacheDir/anotherSubCacheDir', + 'cachePrefix', + $pathResolver + ); + } + + public function testCacheRootPathDirCreationWithoutInvalidSlashes(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir/subRootDir', + 'cachePrefix/subCacheDir' + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir', + 'webRoot', + $pathResolver + ); + $this->assertAttributeSame( + 'cachePrefix/subCacheDir', + 'cachePrefix', + $pathResolver + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir/cachePrefix/subCacheDir', + 'cacheRoot', + $pathResolver + ); + } + + public function testCacheRootPathDirCreationWithInvalidSlashes(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir/subRootDir/', + '/cachePrefix/subCacheDir' + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir', + 'webRoot', + $pathResolver + ); + $this->assertAttributeSame( + 'cachePrefix/subCacheDir', + 'cachePrefix', + $pathResolver + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir/cachePrefix/subCacheDir', + 'cacheRoot', + $pathResolver + ); + } + + public function testCacheRootPathDirCreationWithDoubledSlashes(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir//subRootDir/', + '/cachePrefix//subCacheDir' + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir', + 'webRoot', + $pathResolver + ); + $this->assertAttributeSame( + 'cachePrefix/subCacheDir', + 'cachePrefix', + $pathResolver + ); + $this->assertAttributeSame( + 'aWebRootDir/subRootDir/cachePrefix/subCacheDir', + 'cacheRoot', + $pathResolver + ); + } + + public function testGetCacheRoot(): void + { + $pathResolver = new PathResolver( + 'aWebRootDir', + '/cachePrefix//subCacheDir' + ); + $this->assertSame( + 'aWebRootDir/cachePrefix/subCacheDir', + $pathResolver->getCacheRoot() + ); + } + + public function testGetFileUrlWithSchemePath(): void + { + $path = 'https://path-to-no-where'; + $filter = 'aFilter'; + $cachePrefix = 'aCahcePrefix'; + + $pathResolver = new PathResolver('aWebRootDir', $cachePrefix); + $actualFileUrl = $pathResolver->getFileUrl($path, $filter); + + $this->assertSame(sprintf('%s/%s/https---path-to-no-where', $cachePrefix, $filter), $actualFileUrl); + } + + public function testGetFileUrlPathTrim(): void + { + $path = '/path-to-no-where'; + $filter = 'aFilter'; + $cachePrefix = 'aCahcePrefix'; + + $pathResolver = new PathResolver('aWebRootDir', $cachePrefix); + $actualFileUrl = $pathResolver->getFileUrl($path, $filter); + + $this->assertSame(sprintf('%s/%s/path-to-no-where', $cachePrefix, $filter), $actualFileUrl); + } + + public function testGetFilePathWithSchemePath(): void + { + $path = 'https://path-to-no-where'; + $filter = 'aFilter'; + $webRootDir = 'aWebRootDir'; + $cachePrefix = 'aCahcePrefix'; + + $pathResolver = new PathResolver($webRootDir, $cachePrefix); + $actualFileUrl = $pathResolver->getFilePath($path, $filter); + + $this->assertSame( + sprintf('%s/%s/%s/https---path-to-no-where', $webRootDir, $cachePrefix, $filter), + $actualFileUrl + ); + } + + public function testGetFilePathWithPathTrim(): void + { + $path = '/path-to-no-where'; + $filter = 'aFilter'; + $webRootDir = 'aWebRootDir'; + $cachePrefix = 'aCahcePrefix'; + + $pathResolver = new PathResolver($webRootDir, $cachePrefix); + $actualFileUrl = $pathResolver->getFilePath($path, $filter); + + $this->assertSame( + sprintf('%s/%s/%s/path-to-no-where', $webRootDir, $cachePrefix, $filter), + $actualFileUrl + ); + } +} diff --git a/Utility/Path/PathResolver.php b/Utility/Path/PathResolver.php new file mode 100644 index 000000000..8734cbde8 --- /dev/null +++ b/Utility/Path/PathResolver.php @@ -0,0 +1,73 @@ +webRoot = rtrim(str_replace('//', '/', $webRootDir), '/'); + $this->cachePrefix = ltrim(str_replace('//', '/', $cachePrefix), '/'); + $this->cacheRoot = $this->webRoot.'/'.$this->cachePrefix; + } + + /** + * {@inheritdoc} + */ + public function getFilePath($path, $filter): string + { + return $this->webRoot.'/'.$this->getFullPath($path, $filter); + } + + /** + * {@inheritdoc} + */ + public function getFileUrl($path, $filter): string + { + return PathHelper::filePathToUrlPath($this->getFullPath($path, $filter)); + } + + /** + * {@inheritdoc} + */ + public function getCacheRoot(): string + { + return $this->cacheRoot; + } + + private function getFullPath($path, $filter): string + { + // crude way of sanitizing URL scheme ("protocol") part + $path = str_replace('://', '---', $path); + + return $this->cachePrefix.'/'.$filter.'/'.ltrim($path, '/'); + } +} diff --git a/Utility/Path/PathResolverInterface.php b/Utility/Path/PathResolverInterface.php new file mode 100644 index 000000000..4e227f928 --- /dev/null +++ b/Utility/Path/PathResolverInterface.php @@ -0,0 +1,29 @@ +