Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ jobs:

- name: Run unit test suite
run: composer test

- name: Run PHPStan analysis
run: composer run-script phpstan
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
"ibexa/doctrine-schema": "~5.0.x-dev",
"ibexa/rector": "~5.0.x-dev",
"mikey179/vfsstream": "^1.6",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-symfony": "^2.0",
"phpunit/phpunit": "^9.6"
},
"autoload": {
Expand All @@ -52,7 +55,8 @@
"scripts": {
"fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots",
"check-cs": "@fix-cs --dry-run",
"test": "phpunit"
"test": "phpunit",
"phpstan": "phpstan analyse"
},
"extra": {
"branch-alias": {
Expand Down
31 changes: 31 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
parameters:
ignoreErrors:
-
message: '#^Class Ibexa\\Bundle\\DesignEngine\\DataCollector\\TwigDataCollector extends @final class Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector\.$#'
identifier: class.extendsFinalByPhpDoc
count: 1
path: src/bundle/DataCollector/TwigDataCollector.php

-
message: '#^Parameter \#1 \$provisioner of method Ibexa\\Bundle\\DesignEngine\\DependencyInjection\\Compiler\\AssetPathResolutionPass\:\:preResolveAssetsPaths\(\) expects Ibexa\\DesignEngine\\Asset\\AssetPathProvisionerInterface, object given\.$#'
identifier: argument.type
count: 1
path: src/bundle/DependencyInjection/Compiler/AssetPathResolutionPass.php

-
message: '#^Parameter \#2 \$designPathMap of method Ibexa\\Bundle\\DesignEngine\\DependencyInjection\\Compiler\\AssetPathResolutionPass\:\:preResolveAssetsPaths\(\) expects array\<string, list\<string\>\>, array\|bool\|float\|int\|string\|null given\.$#'
identifier: argument.type
count: 1
path: src/bundle/DependencyInjection/Compiler/AssetPathResolutionPass.php

-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/bundle/DependencyInjection/Compiler/AssetThemePass.php

-
message: '#^PHPDoc tag @var with type SplFileInfo is not subtype of native type Symfony\\Component\\Finder\\SplFileInfo\.$#'
identifier: varTag.nativeType
count: 1
path: src/lib/Asset/ProvisionedPathResolver.php
11 changes: 11 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
includes:
- phpstan-baseline.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-symfony/extension.neon

parameters:
level: 8
paths:
- src
- tests
treatPhpDocTypesAsCertain: false
21 changes: 11 additions & 10 deletions src/bundle/DataCollector/TwigDataCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,30 @@

class TwigDataCollector extends BaseCollector
{
private TemplatePathRegistryInterface $templatePathRegistry;

public function __construct(Profile $profile, Environment $environment, TemplatePathRegistryInterface $templatePathRegistry)
{
public function __construct(
Profile $profile,
Environment $environment,
private TemplatePathRegistryInterface $templatePathRegistry
) {
parent::__construct($profile, $environment);
$this->templatePathRegistry = $templatePathRegistry;
}

private function getTemplatePathRegistry()
private function getTemplatePathRegistry(): TemplatePathRegistryInterface
{
if (!isset($this->templatePathRegistry)) {
$this->templatePathRegistry = unserialize($this->data['template_path_registry']);
}

return $this->templatePathRegistry;
}

#[\Override]
public function lateCollect(): void
{
parent::lateCollect();
$this->data['template_path_registry'] = serialize($this->templatePathRegistry);
}

/**
* @return array<string, int>
*/
#[\Override]
public function getTemplates(): array
{
$registry = $this->getTemplatePathRegistry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public function process(ContainerBuilder $container): void
$container->setAlias('ibexadesign.asset_path_resolver', new Alias(ProvisionedPathResolver::class));
}

/**
* @param array<string, list<string>> $designPathMap
*
* @return array<string, array<string, string>>
*/
private function preResolveAssetsPaths(AssetPathProvisionerInterface $provisioner, array $designPathMap): array
{
$resolvedPathsByDesign = [];
Expand Down
17 changes: 11 additions & 6 deletions src/bundle/DependencyInjection/Compiler/AssetThemePass.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ public function process(ContainerBuilder $container): void
return;
}

$overridePaths = $container->getParameter('ibexa.design.assets.override_paths');
$themesPathMap = [
'_override' => array_merge(
['assets'],
$container->getParameter('ibexa.design.assets.override_paths')
(array)$overridePaths,
),
];
$finder = new Finder();
// Look for assets themes in bundles.
foreach ($container->getParameter('kernel.bundles') as $bundleName => $bundleClass) {
foreach ((array)$container->getParameter('kernel.bundles') as $bundleName => $bundleClass) {
$bundleReflection = new \ReflectionClass($bundleClass);
$bundleViewsDir = \dirname($bundleReflection->getFileName()) . '/Resources/public';
$fileName = $bundleReflection->getFileName();
assert(is_string($fileName));
$bundleViewsDir = \dirname($fileName) . '/Resources/public';
$themeDir = $bundleViewsDir . '/themes';
if (!is_dir($themeDir)) {
continue;
Expand All @@ -46,7 +49,9 @@ public function process(ContainerBuilder $container): void
}

// Look for assets themes at application level (web/assets/themes).
$appLevelThemeDir = $container->getParameter('webroot_dir') . '/assets/themes';
$webrootDir = $container->getParameter('webroot_dir');
assert(is_string($webrootDir));
$appLevelThemeDir = $webrootDir . '/assets/themes';
if (is_dir($appLevelThemeDir)) {
foreach ((new Finder())->directories()->in($appLevelThemeDir)->depth('== 0') as $directoryInfo) {
$theme = $directoryInfo->getBasename();
Expand All @@ -62,7 +67,7 @@ public function process(ContainerBuilder $container): void
}

$pathsByDesign = [];
foreach ($container->getParameter('ibexa.design.list') as $designName => $themeFallback) {
foreach ((array)$container->getParameter('ibexa.design.list') as $designName => $themeFallback) {
// Always add _override theme first.
array_unshift($themeFallback, '_override');
foreach ($themeFallback as $theme) {
Expand All @@ -77,7 +82,7 @@ public function process(ContainerBuilder $container): void
}
}

$themesList = $container->getParameter('ibexa.design.themes.list');
$themesList = (array)$container->getParameter('ibexa.design.themes.list');
$container->setParameter(
'ibexa.design.themes.list',
array_unique(
Expand Down
23 changes: 14 additions & 9 deletions src/bundle/DependencyInjection/Compiler/TwigThemePass.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ public function process(ContainerBuilder $container): void
}

$themesPathMap = [
'_override' => $container->getParameter('ibexa.design.templates.override_paths'),
'_override' => (array)$container->getParameter('ibexa.design.templates.override_paths'),
];
$finder = new Finder();
// Look for themes in bundles.
foreach ($container->getParameter('kernel.bundles') as $bundleName => $bundleClass) {
foreach ((array)$container->getParameter('kernel.bundles') as $bundleName => $bundleClass) {
$bundleReflection = new ReflectionClass($bundleClass);
$bundleViewsDir = \dirname($bundleReflection->getFileName()) . '/Resources/views';
$filename = $bundleReflection->getFileName();
assert(is_string($filename));
$bundleViewsDir = \dirname($filename) . '/Resources/views';
$themeDir = $bundleViewsDir . '/themes';
if (!is_dir($themeDir)) {
continue;
Expand All @@ -53,9 +55,9 @@ public function process(ContainerBuilder $container): void

$twigLoaderDef = $container->findDefinition(TwigThemeLoader::class);
// Now look for themes at application level
$appLevelThemesDir = $container->getParameterBag()->resolveValue(
$container->getParameter('twig.default_path') . '/themes'
);
$twigDefaultPath = $container->getParameter('twig.default_path');
assert(is_string($twigDefaultPath));
$appLevelThemesDir = $container->getParameterBag()->resolveValue($twigDefaultPath . '/themes');

if (is_dir($appLevelThemesDir)) {
foreach ((new Finder())->directories()->in($appLevelThemesDir)->depth('== 0') as $directoryInfo) {
Expand All @@ -69,14 +71,17 @@ public function process(ContainerBuilder $container): void

// Now merge with already configured template theme paths
// Template theme paths defined via config will always have less priority than convention based paths
$themesPathMap = array_merge_recursive($themesPathMap, $container->getParameter('ibexa.design.templates.path_map'));
$themesPathMap = array_merge_recursive(
$themesPathMap,
(array)$container->getParameter('ibexa.design.templates.path_map'),
);

// De-duplicate the map
foreach ($themesPathMap as $theme => &$paths) {
$paths = array_unique($paths);
}

foreach ($container->getParameter('ibexa.design.list') as $designName => $themeFallback) {
foreach ((array)$container->getParameter('ibexa.design.list') as $designName => $themeFallback) {
// Always add _override theme first.
array_unshift($themeFallback, '_override');
foreach ($themeFallback as $theme) {
Expand All @@ -91,7 +96,7 @@ public function process(ContainerBuilder $container): void
}
}

$themesList = $container->getParameter('ibexa.design.themes.list');
$themesList = (array)$container->getParameter('ibexa.design.themes.list');
$container->setParameter(
'ibexa.design.themes.list',
array_unique(
Expand Down
10 changes: 10 additions & 0 deletions src/bundle/DependencyInjection/DesignConfigParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@

class DesignConfigParser implements ParserInterface
{
/**
* @param array<string, mixed> $scopeSettings
* @param string $currentScope
*/
public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerInterface $contextualizer): void
{
if (isset($scopeSettings['design'])) {
$contextualizer->setContextualParameter('design', $currentScope, $scopeSettings['design']);
}
}

/**
* @param array<string, mixed> $config
*/
public function preMap(array $config, ContextualizerInterface $contextualizer): void
{
// Nothing to map
}

/**
* @param array<string, mixed> $config
*/
public function postMap(array $config, ContextualizerInterface $contextualizer): void
{
// Nothing to map
Expand Down
14 changes: 10 additions & 4 deletions src/bundle/DependencyInjection/IbexaDesignEngineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

namespace Ibexa\Bundle\DesignEngine\DependencyInjection;

use Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand All @@ -18,11 +17,16 @@ class IbexaDesignEngineExtension extends Extension
{
public const string EXTENSION_NAME = 'ibexa_design_engine';

#[\Override]
public function getAlias(): string
{
return self::EXTENSION_NAME;
}

/**
* @param array<string, mixed> $config
*/
#[\Override]
public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
{
return new Configuration();
Expand All @@ -40,12 +44,14 @@ public function load(array $configs, ContainerBuilder $container): void
$configuration = $this->getConfiguration($configs, $container);
assert(null !== $configuration);
$config = $this->processConfiguration($configuration, $configs);
$processor = new ConfigurationProcessor($container, 'ezdesign');

$this->configureDesigns($config, $processor, $container);
$this->configureDesigns($config, $container);
}

private function configureDesigns(array $config, ConfigurationProcessor $processor, ContainerBuilder $container): void
/**
* @param array<string, mixed> $config
*/
private function configureDesigns(array $config, ContainerBuilder $container): void
{
// Always add "standard" design to the list (defaults to application level & override paths only)
$config['design_list'] += ['standard' => []];
Expand Down
3 changes: 2 additions & 1 deletion src/bundle/IbexaDesignEngineBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new AssetPathResolutionPass(), PassConfig::TYPE_OPTIMIZE);
}

#[\Override]
public function getContainerExtension(): ?ExtensionInterface
{
if (!isset($this->extension)) {
$this->extension = new IbexaDesignEngineExtension();
}

return $this->extension;
return $this->extension === false ? null : $this->extension;
}
}
2 changes: 1 addition & 1 deletion src/lib/Asset/AssetPathProvisionerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface AssetPathProvisionerInterface
* Returns a map with asset logical path as key and its resolved path (relative to webroot dir) as value.
* Example => ['images/foo.png' => 'asset/themes/some_theme/images/foo.png'].
*
* @param string[] $assetsPaths
* @param list<string> $assetsPaths
*
* @return array<string, string>
*/
Expand Down
20 changes: 8 additions & 12 deletions src/lib/Asset/AssetPathResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@

class AssetPathResolver implements AssetPathResolverInterface
{
/** @var array<string, string> */
private array $designPaths;

private string $webRootDir;

private ?LoggerInterface $logger;

public function __construct(array $designPaths, string $webRootDir, LoggerInterface $logger = null)
{
$this->designPaths = $designPaths;
$this->webRootDir = $webRootDir;
$this->logger = $logger;
/**
* @param array<string, array<int, string>> $designPaths
*/
public function __construct(
private readonly array $designPaths,
private readonly string $webRootDir,
private readonly ?LoggerInterface $logger = null
) {
}

public function resolveAssetPath(string $path, string $design): string
Expand Down
22 changes: 7 additions & 15 deletions src/lib/Asset/ProvisionedPathResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,21 @@

use Symfony\Component\Finder\Finder;

class ProvisionedPathResolver implements AssetPathResolverInterface, AssetPathProvisionerInterface
readonly class ProvisionedPathResolver implements AssetPathResolverInterface, AssetPathProvisionerInterface
{
/**
* @var array<string, array<string, string>>
* @param array<string, array<string, string>> $resolvedPaths
*/
private array $resolvedPaths;

private AssetPathResolverInterface $innerResolver;

private string $webRootDir;

public function __construct(array $resolvedPaths, AssetPathResolverInterface $innerResolver, string $webRootDir)
{
$this->resolvedPaths = $resolvedPaths;
$this->innerResolver = $innerResolver;
$this->webRootDir = $webRootDir;
public function __construct(
private array $resolvedPaths,
private AssetPathResolverInterface $innerResolver,
private string $webRootDir
) {
}

/**
* Looks for $path within pre-resolved paths for provided design.
* If it cannot be found, fallbacks to original resolver.
*
* {@inheritdoc}
*/
public function resolveAssetPath(string $path, string $design): string
{
Expand Down
Loading
Loading