|  | 
|  | 1 | +<?php declare(strict_types = 1); | 
|  | 2 | + | 
|  | 3 | +namespace PHPStan\Nette; | 
|  | 4 | + | 
|  | 5 | +use Nette\Application\IPresenterFactory; | 
|  | 6 | +use Nette\Application\PresenterFactory; | 
|  | 7 | +use PHPStan\Exceptions\LinkCheckFailedException; | 
|  | 8 | +use PHPStan\ShouldNotHappenException; | 
|  | 9 | +use ReflectionClass; | 
|  | 10 | +use ReflectionException; | 
|  | 11 | +use function count; | 
|  | 12 | +use function is_array; | 
|  | 13 | +use function is_string; | 
|  | 14 | +use function preg_match; | 
|  | 15 | +use function preg_replace; | 
|  | 16 | +use function sprintf; | 
|  | 17 | +use function str_replace; | 
|  | 18 | +use function strrpos; | 
|  | 19 | +use function substr; | 
|  | 20 | + | 
|  | 21 | +class PresenterResolver | 
|  | 22 | +{ | 
|  | 23 | + | 
|  | 24 | +	/** @var array<string, string|array{0: string, 1: string, 2: string}> */ | 
|  | 25 | +	protected $mapping; | 
|  | 26 | + | 
|  | 27 | +	/** @var ContainerResolver */ | 
|  | 28 | +	private $containerResolver; | 
|  | 29 | + | 
|  | 30 | +	/** @var IPresenterFactory */ | 
|  | 31 | +	private $presenterFactory; | 
|  | 32 | + | 
|  | 33 | +	/** | 
|  | 34 | +	 * @param array<string, string|array{0: string, 1: string, 2: string}> $mapping | 
|  | 35 | +	 */ | 
|  | 36 | +	public function __construct(array $mapping, ContainerResolver $containerResolver) | 
|  | 37 | +	{ | 
|  | 38 | +		$this->mapping = $mapping; | 
|  | 39 | +		$this->containerResolver = $containerResolver; | 
|  | 40 | +	} | 
|  | 41 | + | 
|  | 42 | +	protected function getPresenterFactory(): IPresenterFactory | 
|  | 43 | +	{ | 
|  | 44 | +		if ($this->presenterFactory === null) { | 
|  | 45 | +			if ($this->containerResolver->getContainer() !== null) { | 
|  | 46 | +				$this->presenterFactory = $this->containerResolver->getContainer()->getByType(IPresenterFactory::class); | 
|  | 47 | +			} else { | 
|  | 48 | +				$this->presenterFactory = new PresenterFactory(); | 
|  | 49 | +				$this->presenterFactory->setMapping($this->mapping); | 
|  | 50 | +			} | 
|  | 51 | +		} | 
|  | 52 | +		return $this->presenterFactory; | 
|  | 53 | +	} | 
|  | 54 | + | 
|  | 55 | +	/** | 
|  | 56 | +	 * @return array<string, array{0: string, 1: string, 2: string}> | 
|  | 57 | +	 * @throws ShouldNotHappenException | 
|  | 58 | +	 * @throws ReflectionException | 
|  | 59 | +	 */ | 
|  | 60 | +	protected function getCurrentMapping(): array | 
|  | 61 | +	{ | 
|  | 62 | +		if ($this->mapping !== []) { | 
|  | 63 | +			$convertedMapping = []; | 
|  | 64 | +			foreach ($this->mapping as $module => $mask) { | 
|  | 65 | +				if (is_string($mask)) { | 
|  | 66 | +					if (preg_match('#^\\\\?([\w\\\\]*\\\\)?(\w*\*\w*?\\\\)?([\w\\\\]*\*\w*)$#D', $mask, $m) !== 1) { | 
|  | 67 | +						throw new ShouldNotHappenException(sprintf("Invalid mapping mask '%s' in parameters.nette.applicationMapping.", $mask)); | 
|  | 68 | +					} | 
|  | 69 | +					$convertedMapping[$module] = [$m[1], $m[2] !== '' ? $m[2] : '*Module\\', $m[3]]; | 
|  | 70 | +				} elseif (is_array($mask) && count($mask) === 3) { /** @phpstan-ignore-line */ | 
|  | 71 | +					$convertedMapping[$module] = [$mask[0] !== '' ? $mask[0] . '\\' : '', $mask[1] . '\\', $mask[2]]; | 
|  | 72 | +				} else { | 
|  | 73 | +					throw new ShouldNotHappenException(sprintf('Invalid mapping mask for module %s in parameters.nette.applicationMapping.', $module)); | 
|  | 74 | +				} | 
|  | 75 | +			} | 
|  | 76 | +			return $convertedMapping; | 
|  | 77 | +		} | 
|  | 78 | + | 
|  | 79 | +		$presenterFactory = $this->getPresenterFactory(); | 
|  | 80 | +		if (!$presenterFactory instanceof PresenterFactory) { | 
|  | 81 | +			throw new ShouldNotHappenException( | 
|  | 82 | +				'PresenterFactory in your container is not instance of Nette\Application\PresenterFactory. We cannot get mapping from it.' . | 
|  | 83 | +				' Either set your mappings explicitly in parameters.nette.applicationMapping ' . | 
|  | 84 | +				' or replace service nettePresenterResolver with your own override of getCurrentMapping() or unformatPresenterClass().' | 
|  | 85 | +			); | 
|  | 86 | +		} | 
|  | 87 | + | 
|  | 88 | +		$mappingPropertyReflection = (new ReflectionClass($presenterFactory))->getProperty('mapping'); | 
|  | 89 | +		$mappingPropertyReflection->setAccessible(true); | 
|  | 90 | +		/** @var array<string, array{0: string, 1: string, 2: string}> $mapping */ | 
|  | 91 | +		$mapping = $mappingPropertyReflection->getValue($presenterFactory); | 
|  | 92 | + | 
|  | 93 | +		return $mapping; | 
|  | 94 | +	} | 
|  | 95 | + | 
|  | 96 | +	public function getPresenterClassByName(string $name, ?string $currentPresenterClass = null): string | 
|  | 97 | +	{ | 
|  | 98 | +		$name = $this->resolvePresenterName($name, $currentPresenterClass); | 
|  | 99 | +		return $this->getPresenterFactory()->getPresenterClass($name); | 
|  | 100 | +	} | 
|  | 101 | + | 
|  | 102 | +	public function resolvePresenterName(string $name, ?string $currentPresenterClass = null): string | 
|  | 103 | +	{ | 
|  | 104 | +		if ($name[0] === ':') { | 
|  | 105 | +			return substr($name, 1); | 
|  | 106 | +		} | 
|  | 107 | + | 
|  | 108 | +		if ($currentPresenterClass === null) { | 
|  | 109 | +			throw new LinkCheckFailedException(sprintf("Cannot resolve relative presenter name '%s' - current presenter is not set.", $name)); | 
|  | 110 | +		} | 
|  | 111 | + | 
|  | 112 | +		$currentName = $this->unformatPresenterClass($currentPresenterClass); | 
|  | 113 | +		$currentNameSepPos = strrpos($currentName, ':'); | 
|  | 114 | +		if ($currentNameSepPos !== false && $currentNameSepPos !== 0) { | 
|  | 115 | +			$currentModule = substr($currentName, 0, $currentNameSepPos); | 
|  | 116 | +			$currentPresenter = substr($currentName, $currentNameSepPos + 1); | 
|  | 117 | +		} else { | 
|  | 118 | +			$currentModule = ''; | 
|  | 119 | +			$currentPresenter = $currentName; | 
|  | 120 | +		} | 
|  | 121 | + | 
|  | 122 | +		if ($name === 'this') { | 
|  | 123 | +			return $currentModule . ':' . $currentPresenter; | 
|  | 124 | +		} | 
|  | 125 | + | 
|  | 126 | +		return $currentModule . ':' . $name; | 
|  | 127 | +	} | 
|  | 128 | + | 
|  | 129 | +	protected function unformatPresenterClass(string $class): string | 
|  | 130 | +	{ | 
|  | 131 | +		foreach ($this->getCurrentMapping() as $module => $mapping) { | 
|  | 132 | +			$mapping = str_replace(['\\', '*'], ['\\\\', '(\w+)'], $mapping); | 
|  | 133 | +			if (preg_match('#^\\\\?' . $mapping[0] . '((?:' . $mapping[1] . ')*)' . $mapping[2] . '$#Di', $class, $matches) === 1) { | 
|  | 134 | +				return ($module === '*' ? '' : $module . ':') | 
|  | 135 | +					. preg_replace('#' . $mapping[1] . '#iA', '$1:', $matches[1]) . $matches[3]; | 
|  | 136 | +			} | 
|  | 137 | +		} | 
|  | 138 | + | 
|  | 139 | +		throw new LinkCheckFailedException(sprintf("Cannot convert presenter class '%s' to presenter name. No matching mapping found.", $class)); | 
|  | 140 | +	} | 
|  | 141 | + | 
|  | 142 | +} | 
0 commit comments