Skip to content

Commit

Permalink
Add support for Twig namespaced paths and templates/ directory
Browse files Browse the repository at this point in the history
  • Loading branch information
pamil committed Oct 9, 2018
1 parent 54c7e8b commit e5a5042
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 69 deletions.
79 changes: 34 additions & 45 deletions src/Sylius/Bundle/ThemeBundle/Locator/BundleResourceLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,12 @@

final class BundleResourceLocator implements ResourceLocatorInterface
{
/**
* @var Filesystem
*/
/** @var Filesystem */
private $filesystem;

/**
* @var KernelInterface
*/
/** @var KernelInterface */
private $kernel;

/**
* @param Filesystem $filesystem
* @param KernelInterface $kernel
*/
public function __construct(Filesystem $filesystem, KernelInterface $kernel)
{
$this->filesystem = $filesystem;
Expand All @@ -41,15 +33,35 @@ public function __construct(Filesystem $filesystem, KernelInterface $kernel)

/**
* {@inheritdoc}
*
* @param string $resourcePath Eg. "@AcmeBundle/Resources/views/template.html.twig"
*/
public function locateResource(string $resourcePath, ThemeInterface $theme): string
{
$this->assertResourcePathIsValid($resourcePath);

$bundleName = $this->getBundleNameFromResourcePath($resourcePath);
$resourceName = $this->getResourceNameFromResourcePath($resourcePath);
if (false !== strpos($resourcePath, 'Bundle/Resources/views/')) {
// When using bundle notation, we get a path like @AcmeBundle/Resources/views/template.html.twig
return $this->locateResourceBasedOnBundleNotation($resourcePath, $theme);
}

// When using namespaced Twig paths, we get a path like @Acme/template.html.twig
return $this->locateResourceBasedOnTwigNamespace($resourcePath, $theme);
}

private function assertResourcePathIsValid(string $resourcePath): void
{
if (0 !== strpos($resourcePath, '@')) {
throw new \InvalidArgumentException(sprintf('Bundle resource path (given "%s") should start with an "@".', $resourcePath));
}

if (false !== strpos($resourcePath, '..')) {
throw new \InvalidArgumentException(sprintf('File name "%s" contains invalid characters (..).', $resourcePath));
}
}

private function locateResourceBasedOnBundleNotation(string $resourcePath, ThemeInterface $theme): string
{
$bundleName = substr($resourcePath, 1, strpos($resourcePath, '/') - 1);
$resourceName = substr($resourcePath, strpos($resourcePath, 'Resources/') + strlen('Resources/'));

// Symfony 4.0+ always returns a single bundle
$bundles = $this->kernel->getBundle($bundleName, false);
Expand All @@ -70,41 +82,18 @@ public function locateResource(string $resourcePath, ThemeInterface $theme): str
throw new ResourceNotFoundException($resourcePath, $theme);
}

/**
* @param string $resourcePath
*/
private function assertResourcePathIsValid(string $resourcePath): void
private function locateResourceBasedOnTwigNamespace(string $resourcePath, ThemeInterface $theme): string
{
if (0 !== strpos($resourcePath, '@')) {
throw new \InvalidArgumentException(sprintf('Bundle resource path (given "%s") should start with an "@".', $resourcePath));
}
$twigNamespace = substr($resourcePath, 1, strpos($resourcePath, '/') - 1);
$bundleName = $twigNamespace . 'Bundle';
$resourceName = substr($resourcePath, strpos($resourcePath, '/') + 1);

if (false !== strpos($resourcePath, '..')) {
throw new \InvalidArgumentException(sprintf('File name "%s" contains invalid characters (..).', $resourcePath));
}
$path = sprintf('%s/%s/views/%s', $theme->getPath(), $bundleName, $resourceName);

if (false === strpos($resourcePath, 'Resources/')) {
throw new \InvalidArgumentException(sprintf('Resource path "%s" should be in bundles\' "Resources/" directory.', $resourcePath));
if ($this->filesystem->exists($path)) {
return $path;
}
}

/**
* @param string $resourcePath
*
* @return string
*/
private function getBundleNameFromResourcePath(string $resourcePath): string
{
return substr($resourcePath, 1, strpos($resourcePath, '/') - 1);
}

/**
* @param string $resourcePath
*
* @return string
*/
private function getResourceNameFromResourcePath(string $resourcePath): string
{
return substr($resourcePath, strpos($resourcePath, 'Resources/') + strlen('Resources/'));
throw new ResourceNotFoundException($resourcePath, $theme);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@
<argument type="service" id="kernel" />
</service>

<service id="sylius.theme.templating.name_parser" class="Sylius\Bundle\ThemeBundle\Templating\TemplateNameParser" decorates="templating.name_parser" decoration-priority="256" public="false">
<argument type="service" id="sylius.theme.templating.name_parser.inner" />
<argument type="service" id="kernel" />
</service>

<service id="sylius.theme.templating.cache" class="Doctrine\Common\Cache\ArrayCache" />

<service id="sylius.theme.templating.locator" class="Sylius\Bundle\ThemeBundle\Templating\Locator\TemplateLocator" public="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function it_implements_resource_locator_interface(): void
$this->shouldImplement(ResourceLocatorInterface::class);
}

function it_locates_bundle_resource(
function it_locates_bundle_resource_using_path_derived_from_bundle_notation_and_symfony3_kernel_behaviour(
Filesystem $filesystem,
KernelInterface $kernel,
ThemeInterface $theme,
Expand All @@ -47,13 +47,30 @@ function it_locates_bundle_resource(

$theme->getPath()->willReturn('/theme/path');

$filesystem->exists('/theme/path/ChildBundle/views/index.html.twig')->shouldBeCalled()->willReturn(false);
$filesystem->exists('/theme/path/ParentBundle/views/index.html.twig')->shouldBeCalled()->willReturn(true);
$filesystem->exists('/theme/path/ChildBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(false);
$filesystem->exists('/theme/path/ParentBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(true);

$this->locateResource('@ParentBundle/Resources/views/index.html.twig', $theme)->shouldReturn('/theme/path/ParentBundle/views/index.html.twig');
$this->locateResource('@ParentBundle/Resources/views/Directory/index.html.twig', $theme)->shouldReturn('/theme/path/ParentBundle/views/Directory/index.html.twig');
}

function it_throws_an_exception_if_resource_can_not_be_located(
function it_locates_bundle_resource_using_path_derived_from_bundle_notation_and_symfony4_kernel_behaviour(
Filesystem $filesystem,
KernelInterface $kernel,
ThemeInterface $theme,
BundleInterface $justBundle
): void {
$kernel->getBundle('JustBundle', false)->willReturn($justBundle);

$justBundle->getName()->willReturn('JustBundle');

$theme->getPath()->willReturn('/theme/path');

$filesystem->exists('/theme/path/JustBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(true);

$this->locateResource('@JustBundle/Resources/views/Directory/index.html.twig', $theme)->shouldReturn('/theme/path/JustBundle/views/Directory/index.html.twig');
}

function it_throws_an_exception_if_resource_can_not_be_located_using_path_derived_from_bundle_notation(
Filesystem $filesystem,
KernelInterface $kernel,
ThemeInterface $theme,
Expand All @@ -68,24 +85,42 @@ function it_throws_an_exception_if_resource_can_not_be_located(
$theme->getName()->willReturn('theme/name');
$theme->getPath()->willReturn('/theme/path');

$filesystem->exists('/theme/path/ChildBundle/views/index.html.twig')->shouldBeCalled()->willReturn(false);
$filesystem->exists('/theme/path/ParentBundle/views/index.html.twig')->shouldBeCalled()->willReturn(false);
$filesystem->exists('/theme/path/ChildBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(false);
$filesystem->exists('/theme/path/ParentBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(false);

$this->shouldThrow(ResourceNotFoundException::class)->during('locateResource', ['@ParentBundle/Resources/views/index.html.twig', $theme]);
$this->shouldThrow(ResourceNotFoundException::class)->during('locateResource', ['@ParentBundle/Resources/views/Directory/index.html.twig', $theme]);
}

function it_throws_an_exception_if_resource_path_does_not_start_with_an_asperand(ThemeInterface $theme): void
{
$this->shouldThrow(\InvalidArgumentException::class)->during('locateResource', ['ParentBundle/Resources/views/index.html.twig', $theme]);
function it_locates_bundle_resource_using_path_derived_from_twig_namespaces(
Filesystem $filesystem,
ThemeInterface $theme
): void {
$theme->getPath()->willReturn('/theme/path');

$filesystem->exists('/theme/path/JustBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(true);

$this->locateResource('@Just/Directory/index.html.twig', $theme)->shouldReturn('/theme/path/JustBundle/views/Directory/index.html.twig');
}

function it_throws_an_exception_if_resource_path_contains_two_dots_in_a_row(ThemeInterface $theme): void
function it_throws_an_exception_if_resource_can_not_be_located_using_path_derived_from_twig_namespaces(
Filesystem $filesystem,
ThemeInterface $theme
): void {
$theme->getName()->willReturn('theme/name');
$theme->getPath()->willReturn('/theme/path');

$filesystem->exists('/theme/path/JustBundle/views/Directory/index.html.twig')->shouldBeCalled()->willReturn(false);

$this->shouldThrow(ResourceNotFoundException::class)->during('locateResource', ['@Just/Directory/index.html.twig', $theme]);
}

function it_throws_an_exception_if_resource_path_does_not_start_with_an_asperand(ThemeInterface $theme): void
{
$this->shouldThrow(\InvalidArgumentException::class)->during('locateResource', ['@ParentBundle/Resources/views/../views/index.html.twig', $theme]);
$this->shouldThrow(\InvalidArgumentException::class)->during('locateResource', ['ParentBundle/Resources/views/Directory/index.html.twig', $theme]);
}

function it_throws_an_exception_if_resource_path_does_not_contain_resources_dir(ThemeInterface $theme): void
function it_throws_an_exception_if_resource_path_contains_two_dots_in_a_row(ThemeInterface $theme): void
{
$this->shouldThrow(\InvalidArgumentException::class)->during('locateResource', ['@ParentBundle/views/Resources.index.html.twig', $theme]);
$this->shouldThrow(\InvalidArgumentException::class)->during('locateResource', ['@ParentBundle/Resources/views/../views/Directory/index.html.twig', $theme]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ function it_proxies_locating_template_to_resource_locator(
TemplateReferenceInterface $template,
ThemeInterface $theme
): void {
$template->getPath()->willReturn('@AcmeBundle/Resources/views/index.html.twig');
$template->getPath()->willReturn('@AcmeBundle/Resources/views/Directory/index.html.twig');

$resourceLocator->locateResource('@AcmeBundle/Resources/views/index.html.twig', $theme)->willReturn('/acme/index.html.twig');
$resourceLocator->locateResource('@AcmeBundle/Resources/views/Directory/index.html.twig', $theme)->willReturn('/acme/index.html.twig');

$this->locateTemplate($template, $theme)->shouldReturn('/acme/index.html.twig');
}
Expand All @@ -49,9 +49,9 @@ function it_does_not_catch_exceptions_thrown_while_locating_template_to_resource
TemplateReferenceInterface $template,
ThemeInterface $theme
): void {
$template->getPath()->willReturn('@AcmeBundle/Resources/views/index.html.twig');
$template->getPath()->willReturn('@AcmeBundle/Resources/views/Directory/index.html.twig');

$resourceLocator->locateResource('@AcmeBundle/Resources/views/index.html.twig', $theme)->willThrow(ResourceNotFoundException::class);
$resourceLocator->locateResource('@AcmeBundle/Resources/views/Directory/index.html.twig', $theme)->willThrow(ResourceNotFoundException::class);

$this->shouldThrow(ResourceNotFoundException::class)->during('locateTemplate', [$template, $theme]);
}
Expand Down

0 comments on commit e5a5042

Please sign in to comment.