-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1096 from robfrawley/feature-add-chain-loader
[Data Loader] [Docs] Add chain loader implementation and related docs
- Loading branch information
Showing
13 changed files
with
543 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the `liip/LiipImagineBundle` project. | ||
* | ||
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors | ||
* | ||
* For the full copyright and license information, please view the LICENSE.md | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Liip\ImagineBundle\Binary\Loader; | ||
|
||
use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; | ||
|
||
class ChainLoader implements LoaderInterface | ||
{ | ||
/** | ||
* @var LoaderInterface[] | ||
*/ | ||
private $loaders; | ||
|
||
/** | ||
* @param LoaderInterface[] $loaders | ||
*/ | ||
public function __construct(array $loaders) | ||
{ | ||
$this->loaders = array_filter($loaders, function ($loader) { | ||
return $loader instanceof LoaderInterface; | ||
}); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function find($path) | ||
{ | ||
$exceptions = []; | ||
|
||
foreach ($this->loaders as $loader) { | ||
try { | ||
return $loader->find($path); | ||
} catch (\Exception $e) { | ||
$exceptions[$e->getMessage()] = $loader; | ||
} | ||
} | ||
|
||
throw new NotLoadableException(self::getLoaderExceptionMessage($path, $exceptions, $this->loaders)); | ||
} | ||
|
||
/** | ||
* @param string $path | ||
* @param \Exception[] $exceptions | ||
* @param array $loaders | ||
* | ||
* @return string | ||
*/ | ||
private static function getLoaderExceptionMessage(string $path, array $exceptions, array $loaders): string | ||
{ | ||
array_walk($loaders, function (LoaderInterface &$loader, string $name): void { | ||
$loader = sprintf('%s=[%s]', (new \ReflectionObject($loader))->getShortName(), $name); | ||
}); | ||
|
||
array_walk($exceptions, function (LoaderInterface &$loader, string $message): void { | ||
$loader = sprintf('%s=[%s]', (new \ReflectionObject($loader))->getShortName(), $message); | ||
}); | ||
|
||
return vsprintf('Source image not resolvable "%s" using "%s" %d loaders (internal exceptions: %s).', [ | ||
$path, | ||
implode(', ', $loaders), | ||
count($loaders), | ||
implode(', ', $exceptions), | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the `liip/LiipImagineBundle` project. | ||
* | ||
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors | ||
* | ||
* For the full copyright and license information, please view the LICENSE.md | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Liip\ImagineBundle\DependencyInjection\Factory\Loader; | ||
|
||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
class ChainLoaderFactory extends AbstractLoaderFactory | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function create(ContainerBuilder $container, $loaderName, array $config): string | ||
{ | ||
$definition = $this->getChildLoaderDefinition(); | ||
$definition->replaceArgument(0, $this->createLoaderReferences($config['loaders'])); | ||
|
||
return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getName(): string | ||
{ | ||
return 'chain'; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function addConfiguration(ArrayNodeDefinition $builder): void | ||
{ | ||
$builder | ||
->children() | ||
->arrayNode('loaders') | ||
->isRequired() | ||
->prototype('scalar') | ||
->cannotBeEmpty() | ||
->end() | ||
->end() | ||
->end(); | ||
} | ||
|
||
/** | ||
* @param string[] $loaders | ||
* | ||
* @return string[] | ||
*/ | ||
private function createLoaderReferences(array $loaders): array | ||
{ | ||
return array_combine($loaders, array_map(function ($name) { | ||
return new Reference(sprintf('liip_imagine.binary.loader.%s', $name)); | ||
}, $loaders)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
|
||
.. _data-loaders-chain: | ||
|
||
Chain Loader | ||
============ | ||
|
||
The ``Chain`` data loader doesn't load the image binary itself; instead | ||
it allows for loading the image binary using any number of other | ||
configured data loaders. For example, if you configured both a | ||
:ref:`filesystem <data-loaders-filesystem>` and | ||
:ref:`flysystem <data-loaders-flysystem>` data loader, this loader can | ||
be defined to load from both in a defined order, returning the image | ||
binary from the first that responds. | ||
|
||
.. tip:: | ||
|
||
This loader iterates over the data loaders in the order they are | ||
configured in the chain definition, returning an image binary from | ||
the first loader that supports the passed file path. This means if | ||
a file exists in more than one loader, the file will be returned | ||
using the first one defined in your configuration file for this | ||
chain loader. | ||
|
||
|
||
|
||
Configuration | ||
------------- | ||
|
||
As this loader leverages any number of other configured loaders, its | ||
configuration is relatively simple; it supports only a ``loaders`` | ||
option that accepts an array of other configured loader names: | ||
|
||
.. code-block:: yaml | ||
# app/config/config.yml | ||
liip_imagine: | ||
loaders: | ||
foo: | ||
filesystem: | ||
# configure filesystem loader | ||
bar: | ||
flysystem: | ||
# configure flysystem loader | ||
baz: | ||
stream: | ||
# configure stream loader | ||
qux: | ||
chain: | ||
# use the "foo", "bar", and "baz" loaders | ||
loaders: | ||
- foo | ||
- bar | ||
- baz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the `liip/LiipImagineBundle` project. | ||
* | ||
* (c) https://github.com/liip/LiipImagineBundle/graphs/contributors | ||
* | ||
* For the full copyright and license information, please view the LICENSE.md | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Liip\ImagineBundle\Tests\Binary\Loader; | ||
|
||
use Liip\ImagineBundle\Binary\Loader\ChainLoader; | ||
use Liip\ImagineBundle\Binary\Loader\FileSystemLoader; | ||
use Liip\ImagineBundle\Binary\Loader\LoaderInterface; | ||
use Liip\ImagineBundle\Binary\Locator\FileSystemLocator; | ||
use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; | ||
use Liip\ImagineBundle\Model\FileBinary; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; | ||
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; | ||
|
||
/** | ||
* @covers \Liip\ImagineBundle\Binary\Loader\ChainLoader | ||
*/ | ||
class ChainLoaderTest extends TestCase | ||
{ | ||
public function testImplementsLoaderInterface(): void | ||
{ | ||
$this->assertInstanceOf(LoaderInterface::class, $this->getChainLoader()); | ||
} | ||
|
||
/** | ||
* @return array[] | ||
*/ | ||
public static function provideLoadCases(): array | ||
{ | ||
$file = pathinfo(__FILE__, PATHINFO_BASENAME); | ||
|
||
return [ | ||
[ | ||
__DIR__, | ||
$file, | ||
], | ||
[ | ||
__DIR__.'/', | ||
$file, | ||
], | ||
[ | ||
__DIR__, '/'. | ||
$file, | ||
], | ||
[ | ||
__DIR__.'/../../Binary/Loader', | ||
'/'.$file, | ||
], | ||
[ | ||
realpath(__DIR__.'/..'), | ||
'Loader/'.$file, | ||
], | ||
[ | ||
__DIR__.'/../', | ||
'/Loader/../../Binary/Loader/'.$file, | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider provideLoadCases | ||
* | ||
* @param string $root | ||
* @param string $path | ||
*/ | ||
public function testLoad(string $root, string $path): void | ||
{ | ||
$this->assertValidLoaderFindReturn($this->getChainLoader([$root])->find($path)); | ||
} | ||
|
||
/** | ||
* @return array[] | ||
*/ | ||
public function provideInvalidPathsData(): array | ||
{ | ||
return [ | ||
['../Loader/../../Binary/Loader/../../../Resources/config/routing.yaml'], | ||
['../../Binary/'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidPathsData | ||
* | ||
* @param string $path | ||
*/ | ||
public function testThrowsIfFileDoesNotExist(string $path): void | ||
{ | ||
$this->expectException(NotLoadableException::class); | ||
$this->expectExceptionMessageRegExp('{Source image not resolvable "[^"]+" using "FileSystemLoader=\[foo\]" 1 loaders}'); | ||
|
||
$this->getChainLoader()->find($path); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidPathsData | ||
* | ||
* @param string $path | ||
*/ | ||
public function testThrowsIfFileDoesNotExistWithMultipleLoaders(string $path): void | ||
{ | ||
$this->expectException(NotLoadableException::class); | ||
$this->expectExceptionMessageRegExp('{Source image not resolvable "[^"]+" using "FileSystemLoader=\[foo\], FileSystemLoader=\[bar\]" 2 loaders \(internal exceptions: FileSystemLoader=\[.+\], FileSystemLoader=\[.+\]\)\.}'); | ||
|
||
$this->getChainLoader([], [ | ||
'foo' => new FileSystemLoader( | ||
MimeTypeGuesser::getInstance(), | ||
ExtensionGuesser::getInstance(), | ||
$this->getFileSystemLocator([ | ||
realpath(__DIR__.'/../../'), | ||
]) | ||
), | ||
'bar' => new FileSystemLoader( | ||
MimeTypeGuesser::getInstance(), | ||
ExtensionGuesser::getInstance(), | ||
$this->getFileSystemLocator([ | ||
realpath(__DIR__.'/../../../'), | ||
]) | ||
), | ||
])->find($path); | ||
} | ||
|
||
/** | ||
* @param string[] $paths | ||
* | ||
* @return FileSystemLocator | ||
*/ | ||
private function getFileSystemLocator(array $paths = []): FileSystemLocator | ||
{ | ||
return new FileSystemLocator($paths); | ||
} | ||
|
||
/** | ||
* @param string[] $paths | ||
* @param FileSystemLoader[] $loaders | ||
* | ||
* @return ChainLoader | ||
*/ | ||
private function getChainLoader(array $paths = [], array $loaders = null): ChainLoader | ||
{ | ||
if (null === $loaders) { | ||
$loaders = [ | ||
'foo' => new FileSystemLoader( | ||
MimeTypeGuesser::getInstance(), | ||
ExtensionGuesser::getInstance(), | ||
$this->getFileSystemLocator($paths ?: [__DIR__]) | ||
), | ||
]; | ||
} | ||
|
||
return new ChainLoader($loaders); | ||
} | ||
|
||
/** | ||
* @param FileBinary|mixed $return | ||
* @param string|null $message | ||
*/ | ||
private function assertValidLoaderFindReturn($return, string $message = null): void | ||
{ | ||
$this->assertInstanceOf(FileBinary::class, $return, $message); | ||
$this->assertStringStartsWith('text/', $return->getMimeType(), $message); | ||
} | ||
} |
Oops, something went wrong.