mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
397 lines
14 KiB
PHP
397 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Chill is a software for social workers
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Chill\MainBundle\DependencyInjection\Widget;
|
|
|
|
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
|
|
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
|
|
use LengthException;
|
|
use LogicException;
|
|
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
use Symfony\Component\DependencyInjection\Definition;
|
|
use Symfony\Component\DependencyInjection\Reference;
|
|
use UnexpectedValueException;
|
|
|
|
use function array_key_exists;
|
|
use function count;
|
|
use function get_class;
|
|
use function in_array;
|
|
use function is_array;
|
|
|
|
/**
|
|
* Compile the configurations and inject required service into container.
|
|
*
|
|
* The widgets are services tagged with :
|
|
*
|
|
* ```
|
|
* { name: chill_widget, alias: my_alias, place: my_place }
|
|
* ```
|
|
*
|
|
* Or, if the tag does not exist or if you need to add some config to your
|
|
* service depending on the config, you should use a `WidgetFactory` (see
|
|
* `WidgetFactoryInterface`.
|
|
*
|
|
* To reuse this compiler pass, simple execute the doProcess metho in your
|
|
* compiler. Example :
|
|
*
|
|
* ```
|
|
* namespace Chill\MainBundle\DependencyInjection\CompilerPass;
|
|
*
|
|
* use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
* use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
|
|
* class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
|
|
*
|
|
* public function process(ContainerBuilder $container)
|
|
* {
|
|
* $this->doProcess($container, 'chill_main', 'chill_main.widgets');
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
|
|
{
|
|
/**
|
|
* the key to use to identify widget for a given place.
|
|
*/
|
|
public const WIDGET_CONFIG_ALIAS = 'widget_alias';
|
|
|
|
/**
|
|
* the key to use to order widget for a given place.
|
|
*/
|
|
public const WIDGET_CONFIG_ORDER = 'order';
|
|
|
|
/**
|
|
* The service which will manage the widgets.
|
|
*
|
|
* @var string
|
|
*/
|
|
public const WIDGET_MANAGER = 'chill.main.twig.widget';
|
|
|
|
/**
|
|
* the method wich register the widget into give service.
|
|
*/
|
|
public const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget';
|
|
|
|
/**
|
|
* the key used to collect the alias in the service definition's tag.
|
|
* the alias must be
|
|
* injected into the configuration under 'alias' key.
|
|
*
|
|
* @var string
|
|
*/
|
|
public const WIDGET_SERVICE_TAG_ALIAS = 'alias';
|
|
|
|
/**
|
|
* the value of the `name` key in service definitions's tag.
|
|
*
|
|
* @var string
|
|
*/
|
|
public const WIDGET_SERVICE_TAG_NAME = 'chill_widget';
|
|
|
|
/**
|
|
* the key used to collect the authorized place in the service definition's tag.
|
|
*
|
|
* @var string
|
|
*/
|
|
public const WIDGET_SERVICE_TAG_PLACES = 'place';
|
|
|
|
/**
|
|
* cache of ordering by place.
|
|
*
|
|
* @internal used by function cacheAndGetOrdering
|
|
*
|
|
* @var array
|
|
*/
|
|
private $cacheOrdering = [];
|
|
|
|
/**
|
|
* @var WidgetFactoryInterface[]
|
|
*/
|
|
private $widgetFactories;
|
|
|
|
private $widgetServices = [];
|
|
|
|
/**
|
|
* process the configuration and the container to add the widget available.
|
|
*
|
|
* @param string $extension the extension of your bundle
|
|
* @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration
|
|
*
|
|
* @throws LogicException
|
|
* @throws UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface
|
|
*/
|
|
public function doProcess(
|
|
ContainerBuilder $container,
|
|
$extension,
|
|
$containerWidgetConfigParameterName
|
|
) {
|
|
if (!$container->hasDefinition(self::WIDGET_MANAGER)) {
|
|
throw new LogicException('the service ' . self::WIDGET_MANAGER . ' should' .
|
|
' be present. It is required by ' . self::class);
|
|
}
|
|
|
|
$managerDefinition = $container->getDefinition(self::WIDGET_MANAGER);
|
|
|
|
// collect the widget factories
|
|
/** @var HasWidgetFactoriesExtensionInterface $extensionClass */
|
|
$extensionClass = $container->getExtension($extension);
|
|
// throw an error if extension does not implement HasWidgetFactoriesExtensionInterface
|
|
if (!$extensionClass instanceof HasWidgetFactoriesExtensionInterface) {
|
|
throw new UnexpectedValueException(sprintf(
|
|
'The extension for %s '
|
|
. 'do not implements %s. You should implement %s on %s',
|
|
$extension,
|
|
HasWidgetFactoriesExtensionInterface::class,
|
|
HasWidgetFactoriesExtensionInterface::class,
|
|
get_class($extensionClass)
|
|
));
|
|
}
|
|
|
|
$this->widgetFactories = $extensionClass->getWidgetFactories();
|
|
|
|
// collect the availabled tagged services
|
|
$this->collectTaggedServices($container);
|
|
|
|
// collect the widgets and their config :
|
|
$widgetParameters = $container->getParameter($containerWidgetConfigParameterName);
|
|
|
|
// and add them to the delegated_block
|
|
foreach ($widgetParameters as $place => $widgets) {
|
|
foreach ($widgets as $param) {
|
|
$alias = $param[self::WIDGET_CONFIG_ALIAS];
|
|
// check that the service exists
|
|
if (!array_key_exists($alias, $this->widgetServices)) {
|
|
throw new InvalidConfigurationException(sprintf('The alias %s' .
|
|
' is not defined.', $alias));
|
|
}
|
|
|
|
// check that the widget is allowed at this place
|
|
if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) {
|
|
throw new InvalidConfigurationException(sprintf(
|
|
'The widget with alias %s is not allowed at place %s',
|
|
$alias,
|
|
$place
|
|
));
|
|
}
|
|
|
|
// get the order, eventually corrected
|
|
$order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]);
|
|
|
|
// register the widget with config to the service, using the method
|
|
// `addWidget`
|
|
if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) {
|
|
/** @var WidgetFactoryInterface $factory */
|
|
$factory = $this->widgetServices[$alias];
|
|
// get the config (under the key which equals to widget_alias
|
|
$config = $param[$factory->getWidgetAlias()] ?? [];
|
|
// register the service into the container
|
|
$serviceId = $this->registerServiceIntoContainer(
|
|
$container,
|
|
$factory,
|
|
$place,
|
|
$order,
|
|
$config
|
|
);
|
|
|
|
$managerDefinition->addMethodCall(
|
|
self::WIDGET_MANAGER_METHOD_REGISTER,
|
|
[
|
|
$place,
|
|
$order,
|
|
new Reference($serviceId),
|
|
$config,
|
|
]
|
|
);
|
|
} else {
|
|
$managerDefinition->addMethodCall(
|
|
self::WIDGET_MANAGER_METHOD_REGISTER,
|
|
[
|
|
$place,
|
|
$order,
|
|
new Reference($this->widgetServices[$alias]),
|
|
[], // the config is alway an empty array
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and
|
|
* add also the widget defined by factories.
|
|
*
|
|
* This method also check that the service is correctly tagged with `alias` and
|
|
* `places`, or the factory give a correct alias and more than one place.
|
|
*
|
|
* @throws InvalidConfigurationException
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
protected function collectTaggedServices(ContainerBuilder $container)
|
|
{
|
|
// first, check the service tagged in service definition
|
|
foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) {
|
|
foreach ($attrs as $attr) {
|
|
// check the alias is set
|
|
if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) {
|
|
throw new InvalidConfigurationException('you should add an ' . self::WIDGET_SERVICE_TAG_ALIAS .
|
|
' key on the service ' . $id);
|
|
}
|
|
|
|
// check the place is set
|
|
if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) {
|
|
throw new InvalidConfigurationException(sprintf(
|
|
'You should add a %s key on the service %s',
|
|
self::WIDGET_SERVICE_TAG_PLACES,
|
|
$id
|
|
));
|
|
}
|
|
|
|
// check the alias does not exists yet
|
|
if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) {
|
|
throw new InvalidArgumentException('a service has already be defined with the ' .
|
|
self::WIDGET_SERVICE_TAG_ALIAS . ' ' . $attr[self::WIDGET_SERVICE_TAG_ALIAS]);
|
|
}
|
|
|
|
// register the service as available
|
|
$this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id;
|
|
}
|
|
}
|
|
|
|
// add the services defined by factories
|
|
foreach ($this->widgetFactories as $factory) {
|
|
/** @var WidgetFactoryInterface $factory */
|
|
$alias = $factory->getWidgetAlias();
|
|
|
|
// check the alias is not empty
|
|
if (empty($alias)) {
|
|
throw new LogicException(sprintf(
|
|
'the widget factory %s returns an empty alias',
|
|
get_class($factory)
|
|
));
|
|
}
|
|
|
|
// check the places are not empty
|
|
if (!is_array($factory->getAllowedPlaces())) {
|
|
throw new UnexpectedValueException("the method 'getAllowedPlaces' "
|
|
. 'should return a non-empty array. Unexpected value on ' .
|
|
get_class($factory));
|
|
}
|
|
|
|
if (count($factory->getAllowedPlaces()) === 0) {
|
|
throw new LengthException("The method 'getAllowedPlaces' should "
|
|
. 'return a non-empty array, but returned 0 elements on ' .
|
|
get_class($factory) . '::getAllowedPlaces()');
|
|
}
|
|
|
|
// check the alias does not exists yet
|
|
if (array_key_exists($alias, $this->widgetServices)) {
|
|
throw new InvalidArgumentException('a service has already be defined with the ' .
|
|
self::WIDGET_SERVICE_TAG_ALIAS . ' ' . $alias);
|
|
}
|
|
|
|
// register the factory as available
|
|
$this->widgetServices[$factory->getWidgetAlias()] = $factory;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* register the service into container.
|
|
*
|
|
* @param string $place
|
|
* @param float $order
|
|
*
|
|
* @return string the id of the new service
|
|
*/
|
|
protected function registerServiceIntoContainer(
|
|
ContainerBuilder $container,
|
|
WidgetFactoryInterface $factory,
|
|
$place,
|
|
$order,
|
|
array $config
|
|
) {
|
|
$serviceId = $factory->getServiceId($container, $place, $order, $config);
|
|
$definition = $factory->createDefinition(
|
|
$container,
|
|
$place,
|
|
$order,
|
|
$config
|
|
);
|
|
$container->setDefinition($serviceId, $definition);
|
|
|
|
return $serviceId;
|
|
}
|
|
|
|
/**
|
|
* check if the ordering has already be used for the given $place and,
|
|
* if yes, correct the ordering by incrementation of 1 until the ordering
|
|
* has not be used.
|
|
*
|
|
* recursive method.
|
|
*
|
|
* @param string $place
|
|
* @param float $ordering
|
|
*
|
|
* @return float
|
|
*/
|
|
private function cacheAndGetOrdering($place, $ordering)
|
|
{
|
|
// create a key in the cache array if not exists
|
|
if (!array_key_exists($place, $this->cacheOrdering)) {
|
|
$this->cacheOrdering[$place] = [];
|
|
}
|
|
|
|
// check if the order exists
|
|
if (array_search($ordering, $this->cacheOrdering[$place], true)) {
|
|
// if the order exists, increment of 1 and try again
|
|
return $this->cacheAndGetOrdering($place, $ordering + 1);
|
|
}
|
|
// cache the ordering
|
|
$this->cacheOrdering[$place][] = $ordering;
|
|
|
|
return $ordering;
|
|
}
|
|
|
|
/**
|
|
* get the places where the service is allowed.
|
|
*
|
|
* @param mixed $place
|
|
* @param mixed $widgetAlias
|
|
*
|
|
* @return unknown
|
|
*/
|
|
private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container)
|
|
{
|
|
if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) {
|
|
if (
|
|
in_array($place, $this->widgetServices[$widgetAlias]
|
|
->getAllowedPlaces(), true)
|
|
) {
|
|
return true;
|
|
}
|
|
} else {
|
|
$definition = $container->findDefinition($this->widgetServices[$widgetAlias]);
|
|
|
|
foreach ($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) {
|
|
$placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES];
|
|
|
|
if ($placeValue === $place) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|