* @author Tobias Nyholm */ class PluginProvider implements Provider { /** * @var Provider */ private $provider; /** * @var Plugin[] */ private $plugins; /** * A list of options. * * @var array{max_restarts?: int<0, max>} */ private $options; /** * @param Plugin[] $plugins * @param array{max_restarts?: int<0, max>} $options */ public function __construct(Provider $provider, array $plugins = [], array $options = []) { $this->provider = $provider; $this->plugins = $plugins; $this->options = $this->configure($options); } public function geocodeQuery(GeocodeQuery $query): Collection { $pluginChain = $this->createPluginChain($this->plugins, function (GeocodeQuery $query) { try { return new GeocoderFulfilledPromise($this->provider->geocodeQuery($query)); } catch (Exception $exception) { return new GeocoderRejectedPromise($exception); } }); return $pluginChain($query)->wait(); } public function reverseQuery(ReverseQuery $query): Collection { $pluginChain = $this->createPluginChain($this->plugins, function (ReverseQuery $query) { try { return new GeocoderFulfilledPromise($this->provider->reverseQuery($query)); } catch (Exception $exception) { return new GeocoderRejectedPromise($exception); } }); return $pluginChain($query)->wait(); } public function getName(): string { return $this->provider->getName(); } /** * Configure the plugin provider. * * @param array{max_restarts?: int<0, max>} $options * * @return array{max_restarts: int<0, max>} */ private function configure(array $options = []): array { $defaults = [ 'max_restarts' => 10, ]; $config = array_merge($defaults, $options); // Make sure no invalid values are provided if (count($config) !== count($defaults)) { throw new LogicException(sprintf('Valid options to the PluginProviders are: %s', implode(', ', array_values($defaults)))); } return $config; } /** * Create the plugin chain. * * @param Plugin[] $pluginList A list of plugins * @param callable $clientCallable Callable making the HTTP call * * @return callable */ private function createPluginChain(array $pluginList, callable $clientCallable) { $firstCallable = $lastCallable = $clientCallable; while ($plugin = array_pop($pluginList)) { $lastCallable = function (Query $query) use ($plugin, $lastCallable, &$firstCallable) { return $plugin->handleQuery($query, $lastCallable, $firstCallable); }; $firstCallable = $lastCallable; } $firstCalls = 0; $firstCallable = function (Query $query) use ($lastCallable, &$firstCalls) { if ($firstCalls > $this->options['max_restarts']) { throw LoopException::create('Too many restarts in plugin provider', $query); } ++$firstCalls; return $lastCallable($query); }; return $firstCallable; } }