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; } }